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
|
||||
before_action :require_login
|
||||
before_action :find_item, except: [:index, :create]
|
||||
before_action :edit_auth, only: [:update, :destroy, :set_public]
|
||||
|
||||
def index
|
||||
default_sort('updated_at', 'desc')
|
||||
|
||||
@items = ItemBankQuery.call(params)
|
||||
@items = paginate courses.includes(:school, :students, :attachments, :homework_commons, teacher: :user_extension)
|
||||
items = ItemBankQuery.call(params)
|
||||
@items_count = items.size
|
||||
@items = paginate items.includes(:item_analysis, :user)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
@ -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
|
||||
# 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 }
|
||||
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :sub_repertoire
|
||||
|
||||
has_one :item_analysis, dependent: :destroy
|
||||
has_many :item_choices, 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
|
||||
|
@ -0,0 +1,4 @@
|
||||
class ItemBankTagRepertoire < ApplicationRecord
|
||||
belongs_to :item_bank
|
||||
belongs_to :tag_repertoire
|
||||
end
|
@ -1,4 +1,14 @@
|
||||
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 :user
|
||||
|
||||
def all_score
|
||||
User.current.item_baskets.map(&:score).sum
|
||||
end
|
||||
|
||||
def question_count
|
||||
User.current.item_baskets.size
|
||||
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