Merge branches 'dev_aliyun' and 'dev_item_bank' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_item_bank
commit
17150f863f
@ -1,14 +1,57 @@
|
|||||||
class LibrariesController < ApplicationController
|
class ItemBanksController < ApplicationController
|
||||||
include PaginateHelper
|
include PaginateHelper
|
||||||
|
before_action :require_login
|
||||||
|
before_action :find_item, except: [:index, :create]
|
||||||
|
before_action :edit_auth, only: [:update, :destroy, :set_public]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
default_sort('updated_at', 'desc')
|
items = ItemBankQuery.call(params)
|
||||||
|
@items_count = items.size
|
||||||
@items = ItemBankQuery.call(params)
|
@items = paginate items.includes(:item_analysis, :user)
|
||||||
@items = paginate courses.includes(:school, :students, :attachments, :homework_commons, teacher: :user_extension)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
item = ItemBank.new(user: current_user)
|
||||||
|
ItemBanks::SaveItemService.call(item, form_params)
|
||||||
|
render_ok
|
||||||
|
rescue ApplicationService::Error => ex
|
||||||
|
render_error(ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
ItemBanks::SaveItemService.call(@item, form_params)
|
||||||
|
render_ok
|
||||||
|
rescue ApplicationService::Error => ex
|
||||||
|
render_error(ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@item.destroy!
|
||||||
|
render_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_public
|
||||||
|
tip_exception(-1, "该试题已公开") if @item.public?
|
||||||
|
@item.update_attributes!(public: 1)
|
||||||
|
render_ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_item
|
||||||
|
@item = ItemBank.find_by!(id: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit_auth
|
||||||
|
current_user.admin_or_business? || @item.user == current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_params
|
||||||
|
params.permit(:repertoire_id, :sub_repertoire_id, :item_type, :difficulty, :name, :analysis, tag_repertoire_id: [], choices: %i[choice_text is_answer])
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -0,0 +1,47 @@
|
|||||||
|
class ItemBasketsController < ApplicationController
|
||||||
|
before_action :require_login
|
||||||
|
|
||||||
|
def index
|
||||||
|
@item_baskets = current_user.item_baskets
|
||||||
|
@single_questions = @item_baskets.where(item_type: "SINGLE")
|
||||||
|
@multiple_questions = @item_baskets.where(item_type: "MULTIPLE")
|
||||||
|
@judgement_questions = @item_baskets.where(item_type: "JUDGMENT")
|
||||||
|
@program_questions = @item_baskets.where(item_type: "PROGRAM")
|
||||||
|
end
|
||||||
|
|
||||||
|
def basket_list
|
||||||
|
@single_questions_count = current_user.item_baskets.where(item_type: "SINGLE").count
|
||||||
|
@multiple_questions_count = current_user.item_baskets.where(item_type: "MULTIPLE").count
|
||||||
|
@judgement_questions_count = current_user.item_baskets.where(item_type: "JUDGMENT").count
|
||||||
|
@completion_questions_count = current_user.item_baskets.where(item_type: "COMPLETION").count
|
||||||
|
@subjective_questions_count = current_user.item_baskets.where(item_type: "SUBJECTIVE").count
|
||||||
|
@practical_questions_count = current_user.item_baskets.where(item_type: "PRACTICAL").count
|
||||||
|
@program_questions_count = current_user.item_baskets.where(item_type: "PROGRAM").count
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ItemBaskets::SaveItemBasketService.call(current_user, create_params)
|
||||||
|
render_ok
|
||||||
|
rescue ApplicationService::Error => ex
|
||||||
|
render_error(ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
item = current_user.item_baskets.find_by!(item_bank_id: params[:id])
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
current_user.item_baskets.where("item_type = #{item.item_type} and position > #{item.position}").update_all("position = position -1")
|
||||||
|
item.destroy!
|
||||||
|
end
|
||||||
|
render_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_item_type
|
||||||
|
# tip_exception() unless
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_params
|
||||||
|
params.permit(item_ids: [])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,33 @@
|
|||||||
|
class ItemBanks::SaveItemForm
|
||||||
|
include ActiveModel::Model
|
||||||
|
|
||||||
|
attr_accessor :repertoire_id, :sub_repertoire_id, :item_type, :difficulty, :name, :analysis, :tag_repertoire_id, :choices
|
||||||
|
|
||||||
|
validates :repertoire_id, presence: true
|
||||||
|
validates :sub_repertoire_id, presence: true
|
||||||
|
validates :item_type, presence: true, inclusion: {in: %W(SINGLE MULTIPLE JUDGMENT COMPLETION SUBJECTIVE PRACTICAL PROGRAM)}
|
||||||
|
validates :difficulty, presence: true, inclusion: {in: 1..3}, numericality: { only_integer: true }
|
||||||
|
validates :name, presence: true, length: { maximum: 1000 }
|
||||||
|
validates :analysis, length: { maximum: 1000 }
|
||||||
|
|
||||||
|
def validate!
|
||||||
|
super
|
||||||
|
return unless errors.blank?
|
||||||
|
choices.each { |item| SubForm.new(item).validate! } if %W(SINGLE MULTIPLE JUDGMENT).include?(item_type)
|
||||||
|
return unless errors.blank?
|
||||||
|
if [0, 2].include?(item_type) && choices.pluck(:is_answer).select{|item| item == 1}.length > 1
|
||||||
|
raise("正确答案只能有一个")
|
||||||
|
elsif item_type == 1 && choices.pluck(:is_answer).select{|item| item == 1}.length <= 1
|
||||||
|
raise("多选题至少有两个正确答案")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SubForm
|
||||||
|
include ActiveModel::Model
|
||||||
|
|
||||||
|
attr_accessor :choice_text, :is_answer
|
||||||
|
|
||||||
|
validates :choice_text, presence: true, length: { maximum: 100 }
|
||||||
|
validates :is_answer, presence: true, inclusion: {in: 0..1}, numericality: { only_integer: true }
|
||||||
|
end
|
||||||
|
end
|
@ -1,11 +1,18 @@
|
|||||||
class ItemBank < ApplicationRecord
|
class ItemBank < ApplicationRecord
|
||||||
# difficulty: 1 简单 2 适中 3 困难
|
# difficulty: 1 简单 2 适中 3 困难
|
||||||
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
|
|
||||||
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
|
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
|
||||||
|
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
belongs_to :sub_repertoire
|
||||||
|
|
||||||
has_one :item_analysis, dependent: :destroy
|
has_one :item_analysis, dependent: :destroy
|
||||||
has_many :item_choices, dependent: :destroy
|
has_many :item_choices, dependent: :destroy
|
||||||
has_many :item_baskets, dependent: :destroy
|
has_many :item_baskets, dependent: :destroy
|
||||||
|
has_many :item_bank_tag_repertoires, dependent: :destroy
|
||||||
|
has_many :tag_repertoires, through: :item_bank_tag_repertoires
|
||||||
|
|
||||||
|
def analysis
|
||||||
|
item_analysis&.analysis
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
class ItemBankTagRepertoire < ApplicationRecord
|
||||||
|
belongs_to :item_bank
|
||||||
|
belongs_to :tag_repertoire
|
||||||
|
end
|
@ -1,4 +1,14 @@
|
|||||||
class ItemBasket < ApplicationRecord
|
class ItemBasket < ApplicationRecord
|
||||||
|
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
|
||||||
|
|
||||||
belongs_to :item_bank
|
belongs_to :item_bank
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
def all_score
|
||||||
|
User.current.item_baskets.map(&:score).sum
|
||||||
|
end
|
||||||
|
|
||||||
|
def question_count
|
||||||
|
User.current.item_baskets.size
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
class ItemBankQuery < ApplicationQuery
|
||||||
|
include CustomSortable
|
||||||
|
attr_reader :params
|
||||||
|
|
||||||
|
sort_columns :updated_at, default_by: :updated_at, default_direction: :desc, default_table: 'item_banks'
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
if params[:public].to_i == 1
|
||||||
|
items = ItemBank.where(public: 1)
|
||||||
|
elsif params[:public].to_i == 0
|
||||||
|
items = ItemBank.where(user_id: User.current.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
if params[:tag_repertoire_id].present?
|
||||||
|
items = items.joins(:item_bank_tag_repertoires).where(item_bank_tag_repertoires: {tag_repertoire_id: params[:tag_repertoire_id]})
|
||||||
|
elsif params[:sub_repertoire_id].present?
|
||||||
|
items = items.where(sub_repertoire_id: params[:sub_repertoire_id])
|
||||||
|
elsif params[:repertoire_id].present?
|
||||||
|
items = items.joins(:sub_repertoire).where(sub_repertoires: {repertoire_id: params[:repertoire_id]})
|
||||||
|
end
|
||||||
|
|
||||||
|
items = items.where(item_type: params[:item_type].to_i) if params[:item_type].present?
|
||||||
|
items = items.where(difficulty: params[:difficulty].to_i) if params[:difficulty].present?
|
||||||
|
|
||||||
|
items = items.where("name like ?", "%#{params[:keyword].strip}%") if params[:keyword].present?
|
||||||
|
|
||||||
|
custom_sort(items, params[:sort_by], params[:sort_direction])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,58 @@
|
|||||||
|
class ItemBanks::SaveItemService < ApplicationService
|
||||||
|
attr_reader :item, :params
|
||||||
|
|
||||||
|
def initialize(item, params)
|
||||||
|
@item = item
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
new_record = item.new_record?
|
||||||
|
raise("不能更改题型") if !new_record && item.item_type != params[:item_type]
|
||||||
|
ItemBanks::SaveItemForm.new(params).validate!
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
item.item_type = params[:item_type] if new_record
|
||||||
|
item.difficulty = params[:difficulty]
|
||||||
|
item.sub_repertoire_id = params[:sub_repertoire_id]
|
||||||
|
item.name = params[:name].strip
|
||||||
|
item.save!
|
||||||
|
|
||||||
|
analysis = item.item_analysis || ItemAnalysis.new(item_bank_id: item.id)
|
||||||
|
analysis.analysis = params[:analysis].blank? ? nil : params[:analysis].strip
|
||||||
|
analysis.save
|
||||||
|
|
||||||
|
# 知识点的创建
|
||||||
|
new_tag_repertoire_ids = params[:tag_repertoire_id]
|
||||||
|
old_tag_repertoire_ids = item.item_bank_tag_repertoires.pluck(:tag_repertoire_id)
|
||||||
|
delete_tag_repertoire_ids = old_tag_repertoire_ids - new_tag_repertoire_ids
|
||||||
|
create_tag_repertoire_ids = new_tag_repertoire_ids - old_tag_repertoire_ids
|
||||||
|
item.item_bank_tag_repertoires.where(tag_repertoire_id: delete_tag_repertoire_ids).destroy_all
|
||||||
|
create_tag_repertoire_ids.each do |tag_id|
|
||||||
|
item.item_bank_tag_repertoires << ItemBankTagRepertoire.new(tag_repertoire_id: tag_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 选项的创建
|
||||||
|
if %W(SINGLE MULTIPLE JUDGMENT).include?(item.item_type)
|
||||||
|
old_choices = item.item_choices
|
||||||
|
new_choices = params[:choices]
|
||||||
|
|
||||||
|
new_choices.each_with_index do |choice, index|
|
||||||
|
choice_item = old_choices[index] || ItemChoice.new(item_bank_id: item.id)
|
||||||
|
choice_item.choice_text = choice[:choice_text]
|
||||||
|
choice_item.is_answer = choice[:is_answer]
|
||||||
|
choice_item.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
if old_choices.length > new_choices.length
|
||||||
|
old_choices[new_choices.length..(old_choices.length-1)].each do |old_choice|
|
||||||
|
old_choice.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
item
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,47 @@
|
|||||||
|
class ItemBaskets::SaveItemBasketService < ApplicationService
|
||||||
|
attr_reader :user, :params
|
||||||
|
|
||||||
|
def initialize(user, params)
|
||||||
|
@user = user
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
raise("请选择试题") if params[:item_ids].blank?
|
||||||
|
|
||||||
|
# 只能选用公共题库或是自己的题库
|
||||||
|
items = ItemBank.where(public: 1).or(ItemBank.where(user_id: user.id))
|
||||||
|
|
||||||
|
# 已选到过试题篮的不重复选用
|
||||||
|
item_ids = params[:item_ids] - user.item_baskets.pluck(:item_bank_id)
|
||||||
|
items.where(id: item_ids).each do |item|
|
||||||
|
new_item = ItemBasket.new(user_id: user.id, item_bank_id: item.id, item_type: item.item_type)
|
||||||
|
new_item.score = item_score item.item_type
|
||||||
|
new_item.position = item_position item.item_type
|
||||||
|
new_item.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def item_score item_type
|
||||||
|
if user.item_baskets.where(item_type: item_type).last.present?
|
||||||
|
score = user.item_baskets.where(item_type: item_type).last.score
|
||||||
|
else
|
||||||
|
score =
|
||||||
|
case item_type
|
||||||
|
when 0, 1, 2
|
||||||
|
5
|
||||||
|
when 6
|
||||||
|
10
|
||||||
|
else
|
||||||
|
5
|
||||||
|
end
|
||||||
|
end
|
||||||
|
score
|
||||||
|
end
|
||||||
|
|
||||||
|
def item_position item_type
|
||||||
|
user.item_baskets.where(item_type: item_type).last&.position.to_i + 1
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,6 @@
|
|||||||
|
json.(item, :id, :name, :item_type, :difficulty, :public, :quotes)
|
||||||
|
json.analysis item.analysis
|
||||||
|
json.choices item.item_choices do |choice|
|
||||||
|
json.choice_text choice.choice_text
|
||||||
|
json.is_answer choice.is_answer
|
||||||
|
end
|
@ -0,0 +1,15 @@
|
|||||||
|
json.repertoire do
|
||||||
|
json.(@item.sub_repertoire&.repertoire, :id, :name)
|
||||||
|
end
|
||||||
|
json.sub_repertoire do
|
||||||
|
json.(@item.sub_repertoire, :id, :name)
|
||||||
|
end
|
||||||
|
json.tag_repertoires @item.tag_repertoires do |tag|
|
||||||
|
json.(tag, :id, :name)
|
||||||
|
end
|
||||||
|
json.(@item, :id, :name, :item_type, :difficulty)
|
||||||
|
json.analysis @item.analysis
|
||||||
|
json.choices @item.item_choices do |choice|
|
||||||
|
json.choice_text choice.choice_text
|
||||||
|
json.is_answer choice.is_answer
|
||||||
|
end
|
@ -0,0 +1,9 @@
|
|||||||
|
json.items @items.each do |item|
|
||||||
|
json.partial! "item_banks/item", locals: {item: item}
|
||||||
|
json.update_time item.updated_at&.strftime("%Y-%m-%d %H:%M")
|
||||||
|
json.author do
|
||||||
|
json.login item.user&.login
|
||||||
|
json.name item.user&.full_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
json.items_count @items_count
|
@ -0,0 +1,7 @@
|
|||||||
|
json.single_questions_count @single_questions_count
|
||||||
|
json.multiple_questions_count @multiple_questions_count
|
||||||
|
json.judgement_questions_count @judgement_questions_count
|
||||||
|
json.completion_questions_count @completion_questions_count
|
||||||
|
json.subjective_questions_count @subjective_questions_count
|
||||||
|
json.practical_questions_count @practical_questions_count
|
||||||
|
json.program_questions_count @program_questions_count
|
@ -0,0 +1,38 @@
|
|||||||
|
json.single_questions do
|
||||||
|
json.questions @single_questions.each do |question|
|
||||||
|
json.(question, :id, :position, :score, :item_type)
|
||||||
|
json.partial! "item_banks/item", locals: {item: question.item_bank}
|
||||||
|
end
|
||||||
|
json.questions_score @single_questions.map(&:score).sum
|
||||||
|
json.questions_count @single_questions.size
|
||||||
|
end
|
||||||
|
|
||||||
|
json.multiple_questions do
|
||||||
|
json.questions @multiple_questions.each do |question|
|
||||||
|
json.(question, :id, :position, :score, :item_type)
|
||||||
|
json.partial! "item_banks/item", locals: {item: question.item_bank}
|
||||||
|
end
|
||||||
|
json.questions_score @multiple_questions.map(&:score).sum
|
||||||
|
json.questions_count @multiple_questions.size
|
||||||
|
end
|
||||||
|
|
||||||
|
json.judgement_questions do
|
||||||
|
json.questions @judgement_questions.each do |question|
|
||||||
|
json.(question, :id, :position, :score, :item_type)
|
||||||
|
json.partial! "item_banks/item", locals: {item: question.item_bank}
|
||||||
|
end
|
||||||
|
json.questions_score @judgement_questions.map(&:score).sum
|
||||||
|
json.questions_count @judgement_questions.size
|
||||||
|
end
|
||||||
|
|
||||||
|
json.program_questions do
|
||||||
|
json.questions @program_questions.each do |question|
|
||||||
|
json.(question, :id, :position, :score, :item_type)
|
||||||
|
json.partial! "item_banks/item", locals: {item: question.item_bank}
|
||||||
|
end
|
||||||
|
json.questions_score @program_questions.map(&:score).sum
|
||||||
|
json.questions_count @program_questions.size
|
||||||
|
end
|
||||||
|
|
||||||
|
json.all_score @item_baskets.map(&:score).sum
|
||||||
|
json.all_questions_count @item_baskets.size
|
@ -0,0 +1,7 @@
|
|||||||
|
class MigrateItemBankColumn < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
remove_column :item_banks, :curriculum_id
|
||||||
|
remove_column :item_banks, :curriculum_direction_id
|
||||||
|
add_column :item_banks, :sub_repertoire_id, :integer, index: true
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,10 @@
|
|||||||
|
class CreateItemBankTagRepertoires < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :item_bank_tag_repertoires do |t|
|
||||||
|
t.references :item_bank, index: true
|
||||||
|
t.references :tag_repertoire, index: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
class ChangeItemBankPublicDefault < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
change_column_default :item_banks, :public, 0
|
||||||
|
change_column_default :item_banks, :quotes, 0
|
||||||
|
change_column_default :item_banks, :difficulty, 1
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
class AddPositionScoreToBasket < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :item_baskets, :position, :integer, default: 0
|
||||||
|
add_column :item_baskets, :score, :float, default: 0
|
||||||
|
add_column :item_baskets, :item_type, :integer, default: 0
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,5 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ItemBankTagRepertoire, type: :model do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
Loading…
Reference in new issue