Extended Exercises by worktime, difficulty and tags, added ProxyExercises as prework for recommendations
Tags can be added to exercises in the edit view. Tags can monitored under /tags. Added the concept of ProxyExercises which are a collection of Exercises. They can be found under /proxy_exercises Added Interventions as prework to show interventions later to the user. Added exercise/[:id]/working_time to return the working time of the user in this exercise and the average working time of all users in this exercise
This commit is contained in:
@ -6,7 +6,7 @@ class ExercisesController < ApplicationController
|
|||||||
|
|
||||||
before_action :handle_file_uploads, only: [:create, :update]
|
before_action :handle_file_uploads, only: [:create, :update]
|
||||||
before_action :set_execution_environments, only: [:create, :edit, :new, :update]
|
before_action :set_execution_environments, only: [:create, :edit, :new, :update]
|
||||||
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit, :reload]
|
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :working_times, :run, :statistics, :submit, :reload]
|
||||||
before_action :set_external_user, only: [:statistics]
|
before_action :set_external_user, only: [:statistics]
|
||||||
before_action :set_file_types, only: [:create, :edit, :new, :update]
|
before_action :set_file_types, only: [:create, :edit, :new, :update]
|
||||||
|
|
||||||
@ -54,6 +54,20 @@ class ExercisesController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@exercise = Exercise.new(exercise_params)
|
@exercise = Exercise.new(exercise_params)
|
||||||
|
collect_set_and_unset_exercise_tags
|
||||||
|
myparam = exercise_params
|
||||||
|
checked_exercise_tags = @exercise_tags.select { | et | myparam[:tag_ids].include? et.tag.id.to_s }
|
||||||
|
removed_exercise_tags = @exercise_tags.reject { | et | myparam[:tag_ids].include? et.tag.id.to_s }
|
||||||
|
|
||||||
|
for et in checked_exercise_tags
|
||||||
|
et.factor = params[:tag_factors][et.tag_id.to_s][:factor]
|
||||||
|
et.exercise = @exercise
|
||||||
|
end
|
||||||
|
|
||||||
|
myparam[:exercise_tags] = checked_exercise_tags
|
||||||
|
myparam.delete :tag_ids
|
||||||
|
removed_exercise_tags.map {|et| et.destroy}
|
||||||
|
|
||||||
authorize!
|
authorize!
|
||||||
create_and_respond(object: @exercise)
|
create_and_respond(object: @exercise)
|
||||||
end
|
end
|
||||||
@ -63,6 +77,7 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
collect_set_and_unset_exercise_tags
|
||||||
end
|
end
|
||||||
|
|
||||||
def import_proforma_xml
|
def import_proforma_xml
|
||||||
@ -118,7 +133,8 @@ class ExercisesController < ApplicationController
|
|||||||
private :user_by_code_harbor_token
|
private :user_by_code_harbor_token
|
||||||
|
|
||||||
def exercise_params
|
def exercise_params
|
||||||
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
params[:exercise][:expected_worktime_seconds] = params[:exercise][:expected_worktime_minutes].to_i * 60
|
||||||
|
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, :expected_worktime_seconds, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||||
end
|
end
|
||||||
private :exercise_params
|
private :exercise_params
|
||||||
|
|
||||||
@ -150,6 +166,12 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def working_times
|
||||||
|
working_time_accumulated = Time.parse(@exercise.average_working_time_for_only(current_user.id) || "00:00:00").seconds_since_midnight
|
||||||
|
working_time_avg = Time.parse(@exercise.average_working_time || "00:00:00").seconds_since_midnight
|
||||||
|
render(json: {working_time_avg: working_time_avg, working_time_accumulated: working_time_accumulated})
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = policy_scope(Exercise).search(params[:q])
|
@search = policy_scope(Exercise).search(params[:q])
|
||||||
@exercises = @search.result.includes(:execution_environment, :user).order(:title).paginate(page: params[:page])
|
@exercises = @search.result.includes(:execution_environment, :user).order(:title).paginate(page: params[:page])
|
||||||
@ -174,6 +196,8 @@ class ExercisesController < ApplicationController
|
|||||||
|
|
||||||
def new
|
def new
|
||||||
@exercise = Exercise.new
|
@exercise = Exercise.new
|
||||||
|
collect_set_and_unset_exercise_tags
|
||||||
|
|
||||||
authorize!
|
authorize!
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -201,6 +225,16 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
private :set_file_types
|
private :set_file_types
|
||||||
|
|
||||||
|
def collect_set_and_unset_exercise_tags
|
||||||
|
@search = policy_scope(Tag).search(params[:q])
|
||||||
|
@tags = @search.result.order(:name)
|
||||||
|
exercise_tags = @exercise.exercise_tags
|
||||||
|
tags_set = exercise_tags.collect{|e| e.tag}.to_set
|
||||||
|
tags_not_set = Tag.all.to_set.subtract tags_set
|
||||||
|
@exercise_tags = exercise_tags + tags_not_set.collect { |tag| ExerciseTag.new(exercise: @exercise, tag: tag)}
|
||||||
|
end
|
||||||
|
private :collect_set_and_unset_exercise_tags
|
||||||
|
|
||||||
def show
|
def show
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -252,7 +286,20 @@ class ExercisesController < ApplicationController
|
|||||||
private :transmit_lti_score
|
private :transmit_lti_score
|
||||||
|
|
||||||
def update
|
def update
|
||||||
update_and_respond(object: @exercise, params: exercise_params)
|
collect_set_and_unset_exercise_tags
|
||||||
|
myparam = exercise_params
|
||||||
|
checked_exercise_tags = @exercise_tags.select { | et | myparam[:tag_ids].include? et.tag.id.to_s }
|
||||||
|
removed_exercise_tags = @exercise_tags.reject { | et | myparam[:tag_ids].include? et.tag.id.to_s }
|
||||||
|
|
||||||
|
for et in checked_exercise_tags
|
||||||
|
et.factor = params[:tag_factors][et.tag_id.to_s][:factor]
|
||||||
|
et.exercise = @exercise
|
||||||
|
end
|
||||||
|
|
||||||
|
myparam[:exercise_tags] = checked_exercise_tags
|
||||||
|
myparam.delete :tag_ids
|
||||||
|
removed_exercise_tags.map {|et| et.destroy}
|
||||||
|
update_and_respond(object: @exercise, params: myparam)
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirect_after_submit
|
def redirect_after_submit
|
||||||
|
80
app/controllers/proxy_exercises_controller.rb
Normal file
80
app/controllers/proxy_exercises_controller.rb
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
class ProxyExercisesController < ApplicationController
|
||||||
|
include CommonBehavior
|
||||||
|
|
||||||
|
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :reload]
|
||||||
|
|
||||||
|
def authorize!
|
||||||
|
authorize(@proxy_exercise || @proxy_exercises)
|
||||||
|
end
|
||||||
|
private :authorize!
|
||||||
|
|
||||||
|
def clone
|
||||||
|
proxy_exercise = @proxy_exercise.duplicate(token: nil, exercises: @proxy_exercise.exercises)
|
||||||
|
proxy_exercise.send(:generate_token)
|
||||||
|
if proxy_exercise.save
|
||||||
|
redirect_to(proxy_exercise, notice: t('shared.object_cloned', model: ProxyExercise.model_name.human))
|
||||||
|
else
|
||||||
|
flash[:danger] = t('shared.message_failure')
|
||||||
|
redirect_to(@proxy_exercise)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
myparams = proxy_exercise_params
|
||||||
|
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject { |c| c.empty? })
|
||||||
|
@proxy_exercise = ProxyExercise.new(myparams)
|
||||||
|
authorize!
|
||||||
|
|
||||||
|
create_and_respond(object: @proxy_exercise)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
destroy_and_respond(object: @proxy_exercise)
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@search = policy_scope(Exercise).search(params[:q])
|
||||||
|
@exercises = @search.result.order(:title)
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_exercise_params
|
||||||
|
params[:proxy_exercise].permit(:description, :title, :exercise_ids => [])
|
||||||
|
end
|
||||||
|
private :proxy_exercise_params
|
||||||
|
|
||||||
|
def index
|
||||||
|
@search = policy_scope(ProxyExercise).search(params[:q])
|
||||||
|
@proxy_exercises = @search.result.order(:title).paginate(page: params[:page])
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@proxy_exercise = ProxyExercise.new
|
||||||
|
@search = policy_scope(Exercise).search(params[:q])
|
||||||
|
@exercises = @search.result.order(:title)
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_exercise
|
||||||
|
@proxy_exercise = ProxyExercise.find(params[:id])
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
private :set_exercise
|
||||||
|
|
||||||
|
def show
|
||||||
|
@search = @proxy_exercise.exercises.search
|
||||||
|
@exercises = @proxy_exercise.exercises.search.result.order(:title) #@search.result.order(:title)
|
||||||
|
end
|
||||||
|
|
||||||
|
#we might want to think about auth here
|
||||||
|
def reload
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
myparams = proxy_exercise_params
|
||||||
|
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject { |c| c.blank? })
|
||||||
|
update_and_respond(object: @proxy_exercise, params: myparams)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
55
app/controllers/tags_controller.rb
Normal file
55
app/controllers/tags_controller.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
class TagsController < ApplicationController
|
||||||
|
include CommonBehavior
|
||||||
|
|
||||||
|
before_action :set_tag, only: MEMBER_ACTIONS
|
||||||
|
|
||||||
|
def authorize!
|
||||||
|
authorize(@tag || @tags)
|
||||||
|
end
|
||||||
|
private :authorize!
|
||||||
|
|
||||||
|
def create
|
||||||
|
@tag = Tag.new(tag_params)
|
||||||
|
authorize!
|
||||||
|
create_and_respond(object: @tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
destroy_and_respond(object: @tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_params
|
||||||
|
params[:tag].permit(:name)
|
||||||
|
end
|
||||||
|
private :tag_params
|
||||||
|
|
||||||
|
def index
|
||||||
|
@tags = Tag.all.paginate(page: params[:page])
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@tag = Tag.new
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_tag
|
||||||
|
@tag = Tag.find(params[:id])
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
private :set_tag
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
update_and_respond(object: @tag, params: tag_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
@ -8,6 +8,10 @@ module User
|
|||||||
has_many :exercises, as: :user
|
has_many :exercises, as: :user
|
||||||
has_many :file_types, as: :user
|
has_many :file_types, as: :user
|
||||||
has_many :submissions, as: :user
|
has_many :submissions, as: :user
|
||||||
|
has_many :user_proxy_exercise_exercises, as: :user
|
||||||
|
has_many :user_exercise_interventions, as: :user
|
||||||
|
has_many :interventions, through: :user_exercise_interventions
|
||||||
|
|
||||||
|
|
||||||
scope :with_submissions, -> { where('id IN (SELECT user_id FROM submissions)') }
|
scope :with_submissions, -> { where('id IN (SELECT user_id FROM submissions)') }
|
||||||
end
|
end
|
||||||
|
@ -12,6 +12,15 @@ class Exercise < ActiveRecord::Base
|
|||||||
belongs_to :execution_environment
|
belongs_to :execution_environment
|
||||||
has_many :submissions
|
has_many :submissions
|
||||||
|
|
||||||
|
has_and_belongs_to_many :proxy_exercises
|
||||||
|
has_many :user_proxy_exercise_exercises
|
||||||
|
has_and_belongs_to_many :exercise_collections
|
||||||
|
has_many :user_exercise_interventions
|
||||||
|
has_many :interventions, through: :user_exercise_interventions
|
||||||
|
has_many :exercise_tags
|
||||||
|
has_many :tags, through: :exercise_tags
|
||||||
|
accepts_nested_attributes_for :exercise_tags
|
||||||
|
|
||||||
has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions
|
has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions
|
||||||
has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions
|
has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions
|
||||||
alias_method :users, :external_users
|
alias_method :users, :external_users
|
||||||
@ -105,6 +114,7 @@ class Exercise < ActiveRecord::Base
|
|||||||
def duplicate(attributes = {})
|
def duplicate(attributes = {})
|
||||||
exercise = dup
|
exercise = dup
|
||||||
exercise.attributes = attributes
|
exercise.attributes = attributes
|
||||||
|
exercise_tags.each { |et| exercise.exercise_tags << et.dup }
|
||||||
files.each { |file| exercise.files << file.dup }
|
files.each { |file| exercise.files << file.dup }
|
||||||
exercise
|
exercise
|
||||||
end
|
end
|
||||||
|
5
app/models/exercise_collection.rb
Normal file
5
app/models/exercise_collection.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class ExerciseCollection < ActiveRecord::Base
|
||||||
|
|
||||||
|
has_and_belongs_to_many :exercises
|
||||||
|
|
||||||
|
end
|
13
app/models/exercise_tag.rb
Normal file
13
app/models/exercise_tag.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class ExerciseTag < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :tag
|
||||||
|
belongs_to :exercise
|
||||||
|
|
||||||
|
before_save :destroy_if_empty_exercise_or_tag
|
||||||
|
|
||||||
|
private
|
||||||
|
def destroy_if_empty_exercise_or_tag
|
||||||
|
destroy if exercise_id.blank? || tag_id.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
15
app/models/intervention.rb
Normal file
15
app/models/intervention.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
class Intervention < ActiveRecord::Base
|
||||||
|
|
||||||
|
NAME = %w(overallSlower longSession syntaxErrors videoNotWatched)
|
||||||
|
|
||||||
|
has_many :user_exercise_interventions
|
||||||
|
has_many :users, through: :user_exercise_interventions, source_type: "ExternalUser"
|
||||||
|
#belongs_to :user, polymorphic: true
|
||||||
|
#belongs_to :external_users, source: :user, source_type: ExternalUser
|
||||||
|
#belongs_to :internal_users, source: :user, source_type: InternalUser, through: :user_interventions
|
||||||
|
# alias_method :users, :external_users
|
||||||
|
#has_many :exercises, through: :user_interventions
|
||||||
|
|
||||||
|
validates :name, inclusion: {in: NAME}
|
||||||
|
|
||||||
|
end
|
27
app/models/proxy_exercise.rb
Normal file
27
app/models/proxy_exercise.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
class ProxyExercise < ActiveRecord::Base
|
||||||
|
|
||||||
|
after_initialize :generate_token
|
||||||
|
|
||||||
|
has_and_belongs_to_many :exercises
|
||||||
|
has_many :user_proxy_exercise_exercises
|
||||||
|
|
||||||
|
def count_files
|
||||||
|
exercises.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_token
|
||||||
|
self.token ||= SecureRandom.hex(4)
|
||||||
|
end
|
||||||
|
private :generate_token
|
||||||
|
|
||||||
|
def duplicate(attributes = {})
|
||||||
|
proxy_exercise = dup
|
||||||
|
proxy_exercise.attributes = attributes
|
||||||
|
proxy_exercise
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
title
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
22
app/models/tag.rb
Normal file
22
app/models/tag.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
class Tag < ActiveRecord::Base
|
||||||
|
|
||||||
|
has_many :exercise_tags
|
||||||
|
has_many :exercises, through: :exercise_tags
|
||||||
|
|
||||||
|
validates_uniqueness_of :name
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if (can_be_destroyed?)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_be_destroyed?
|
||||||
|
!exercises.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
8
app/models/user_exercise_feedback.rb
Normal file
8
app/models/user_exercise_feedback.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class UserExerciseFeedback < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :user, polymorphic: true
|
||||||
|
belongs_to :exercise
|
||||||
|
|
||||||
|
validates :user_id, uniqueness: { scope: [:exercise_id, :user_type] }
|
||||||
|
|
||||||
|
end
|
11
app/models/user_exercise_intervention.rb
Normal file
11
app/models/user_exercise_intervention.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class UserExerciseIntervention < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :user, polymorphic: true
|
||||||
|
belongs_to :intervention
|
||||||
|
belongs_to :exercise
|
||||||
|
|
||||||
|
validates :user, presence: true
|
||||||
|
validates :exercise, presence: true
|
||||||
|
validates :intervention, presence: true
|
||||||
|
|
||||||
|
end
|
14
app/models/user_proxy_exercise_exercise.rb
Normal file
14
app/models/user_proxy_exercise_exercise.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class UserProxyExerciseExercise < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :user, polymorphic: true
|
||||||
|
belongs_to :exercise
|
||||||
|
belongs_to :proxy_exercise
|
||||||
|
|
||||||
|
validates :user_id, presence: true
|
||||||
|
validates :user_type, presence: true
|
||||||
|
validates :exercise_id, presence: true
|
||||||
|
validates :proxy_exercise_id, presence: true
|
||||||
|
|
||||||
|
validates :user_id, uniqueness: { scope: [:proxy_exercise_id, :user_type] }
|
||||||
|
|
||||||
|
end
|
@ -16,7 +16,7 @@ class ExercisePolicy < AdminOrAuthorPolicy
|
|||||||
define_method(action) { admin? || author?}
|
define_method(action) { admin? || author?}
|
||||||
end
|
end
|
||||||
|
|
||||||
[:implement?, :submit?, :reload?].each do |action|
|
[:implement?, :working_times?, :submit?, :reload?].each do |action|
|
||||||
define_method(action) { everyone }
|
define_method(action) { everyone }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
34
app/policies/proxy_exercise_policy.rb
Normal file
34
app/policies/proxy_exercise_policy.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class ProxyExercisePolicy < AdminOrAuthorPolicy
|
||||||
|
def author?
|
||||||
|
@user == @record.author
|
||||||
|
end
|
||||||
|
private :author?
|
||||||
|
|
||||||
|
def batch_update?
|
||||||
|
admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
@user.internal_user?
|
||||||
|
end
|
||||||
|
|
||||||
|
[:clone?, :destroy?, :edit?, :update?].each do |action|
|
||||||
|
define_method(action) { admin? || author?}
|
||||||
|
end
|
||||||
|
|
||||||
|
[:reload?].each do |action|
|
||||||
|
define_method(action) { everyone }
|
||||||
|
end
|
||||||
|
|
||||||
|
class Scope < Scope
|
||||||
|
def resolve
|
||||||
|
if @user.admin?
|
||||||
|
@scope.all
|
||||||
|
elsif @user.internal_user?
|
||||||
|
@scope.where('user_id = ? OR public = TRUE', @user.id)
|
||||||
|
else
|
||||||
|
@scope.none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
34
app/policies/tag_policy.rb
Normal file
34
app/policies/tag_policy.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class TagPolicy < AdminOrAuthorPolicy
|
||||||
|
def author?
|
||||||
|
@user == @record.author
|
||||||
|
end
|
||||||
|
private :author?
|
||||||
|
|
||||||
|
def batch_update?
|
||||||
|
admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
@user.internal_user?
|
||||||
|
end
|
||||||
|
|
||||||
|
[:clone?, :destroy?, :edit?, :update?].each do |action|
|
||||||
|
define_method(action) { admin? || author?}
|
||||||
|
end
|
||||||
|
|
||||||
|
[:reload?].each do |action|
|
||||||
|
define_method(action) { everyone }
|
||||||
|
end
|
||||||
|
|
||||||
|
class Scope < Scope
|
||||||
|
def resolve
|
||||||
|
if @user.admin?
|
||||||
|
@scope.all
|
||||||
|
elsif @user.internal_user?
|
||||||
|
@scope.where('user_id = ? OR public = TRUE', @user.id)
|
||||||
|
else
|
||||||
|
@scope.none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -21,4 +21,4 @@
|
|||||||
button style="display:none" id="autosave"
|
button style="display:none" id="autosave"
|
||||||
|
|
||||||
|
|
||||||
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
|
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
|
@ -32,6 +32,25 @@
|
|||||||
label
|
label
|
||||||
= f.check_box(:allow_auto_completion)
|
= f.check_box(:allow_auto_completion)
|
||||||
= t('activerecord.attributes.exercise.allow_auto_completion')
|
= t('activerecord.attributes.exercise.allow_auto_completion')
|
||||||
|
.form-group
|
||||||
|
= f.label(t('activerecord.attributes.exercise.difficulty'))
|
||||||
|
= f.number_field :expected_difficulty, in: 1..10, step: 1
|
||||||
|
.form-group
|
||||||
|
= f.label(t('activerecord.attributes.exercise.worktime'))
|
||||||
|
= f.number_field "expected_worktime_minutes", value: @exercise.expected_worktime_seconds / 60, in: 1..1000, step: 1
|
||||||
|
h2 Tags
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = t('activerecord.attributes.exercise.selection')
|
||||||
|
th = sort_link(@search, :title, t('activerecord.attributes.tag.name'))
|
||||||
|
th = t('activerecord.attributes.tag.difficulty')
|
||||||
|
= collection_check_boxes :exercise, :tag_ids, @exercise_tags, :tag_id, :id do |b|
|
||||||
|
tr
|
||||||
|
td = b.check_box
|
||||||
|
td = b.object.tag.name
|
||||||
|
td = number_field "tag_factors[#{b.object.tag.id}]", :factor, :value => b.object.factor, in: 1..10, step: 1
|
||||||
h2 = t('activerecord.attributes.exercise.files')
|
h2 = t('activerecord.attributes.exercise.files')
|
||||||
ul#files.list-unstyled.panel-group
|
ul#files.list-unstyled.panel-group
|
||||||
= f.fields_for :files do |files_form|
|
= f.fields_for :files do |files_form|
|
||||||
|
@ -22,3 +22,4 @@
|
|||||||
#questions-column
|
#questions-column
|
||||||
#questions-holder data-url="#{qa_url}/qa/index/#{@exercise.id}/#{@user_id}"
|
#questions-holder data-url="#{qa_url}/qa/index/#{@exercise.id}/#{@user_id}"
|
||||||
= qa_js_tag
|
= qa_js_tag
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ h1 = Exercise.model_name.human(count: 2)
|
|||||||
th = sort_link(@search, :execution_environment_id, t('activerecord.attributes.exercise.execution_environment'))
|
th = sort_link(@search, :execution_environment_id, t('activerecord.attributes.exercise.execution_environment'))
|
||||||
th = t('.test_files')
|
th = t('.test_files')
|
||||||
th = t('activerecord.attributes.exercise.maximum_score')
|
th = t('activerecord.attributes.exercise.maximum_score')
|
||||||
|
th = t('activerecord.attributes.exercise.tags')
|
||||||
|
th = t('activerecord.attributes.exercise.difficulty')
|
||||||
|
th = t('activerecord.attributes.exercise.worktime')
|
||||||
th
|
th
|
||||||
= t('activerecord.attributes.exercise.public')
|
= t('activerecord.attributes.exercise.public')
|
||||||
- if policy(Exercise).batch_update?
|
- if policy(Exercise).batch_update?
|
||||||
@ -29,6 +32,9 @@ h1 = Exercise.model_name.human(count: 2)
|
|||||||
td = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
|
td = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
|
||||||
td = exercise.files.teacher_defined_tests.count
|
td = exercise.files.teacher_defined_tests.count
|
||||||
td = exercise.maximum_score
|
td = exercise.maximum_score
|
||||||
|
td = exercise.exercise_tags.count
|
||||||
|
td = exercise.expected_difficulty
|
||||||
|
td = (exercise.expected_worktime_seconds / 60).ceil
|
||||||
td.public data-value=exercise.public? = symbol_for(exercise.public?)
|
td.public data-value=exercise.public? = symbol_for(exercise.public?)
|
||||||
td = link_to(t('shared.edit'), edit_exercise_path(exercise)) if policy(exercise).edit?
|
td = link_to(t('shared.edit'), edit_exercise_path(exercise)) if policy(exercise).edit?
|
||||||
td = link_to(t('.implement'), implement_exercise_path(exercise)) if policy(exercise).implement?
|
td = link_to(t('.implement'), implement_exercise_path(exercise)) if policy(exercise).implement?
|
||||||
|
@ -19,6 +19,9 @@ h1
|
|||||||
= row(label: 'exercise.allow_auto_completion', value: @exercise.allow_auto_completion?)
|
= row(label: 'exercise.allow_auto_completion', value: @exercise.allow_auto_completion?)
|
||||||
= row(label: 'exercise.embedding_parameters') do
|
= row(label: 'exercise.embedding_parameters') do
|
||||||
= content_tag(:input, nil, class: 'form-control', readonly: true, value: embedding_parameters(@exercise))
|
= content_tag(:input, nil, class: 'form-control', readonly: true, value: embedding_parameters(@exercise))
|
||||||
|
= row(label: 'exercise.difficulty', value: @exercise.expected_difficulty)
|
||||||
|
= row(label: 'exercise.worktime', value: "#{@exercise.expected_worktime_seconds/60} min")
|
||||||
|
= row(label: 'exercise.tags', value: @exercise.exercise_tags.map{|et| "#{et.tag.name} (#{et.factor})"}.sort.join(", "))
|
||||||
|
|
||||||
h2 = t('activerecord.attributes.exercise.files')
|
h2 = t('activerecord.attributes.exercise.files')
|
||||||
|
|
||||||
|
24
app/views/proxy_exercises/_form.html.slim
Normal file
24
app/views/proxy_exercises/_form.html.slim
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
= form_for(@proxy_exercise, multipart: true) do |f|
|
||||||
|
= render('shared/form_errors', object: @proxy_exercise)
|
||||||
|
.form-group
|
||||||
|
= f.label(:title)
|
||||||
|
= f.text_field(:title, class: 'form-control', required: true)
|
||||||
|
.form-group
|
||||||
|
= f.label(:description)
|
||||||
|
= f.pagedown_editor :description
|
||||||
|
|
||||||
|
h3 Exercises
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = t('activerecord.attributes.exercise.selection')
|
||||||
|
th = sort_link(@search, :title, t('activerecord.attributes.submission.exercise'))
|
||||||
|
th = sort_link(@search, :created_at, t('shared.created_at'))
|
||||||
|
= collection_check_boxes :proxy_exercise, :exercise_ids, @exercises, :id, :title do |b|
|
||||||
|
tr
|
||||||
|
td = b.check_box
|
||||||
|
td = link_to(b.object, b.object)
|
||||||
|
td = l(b.object.created_at, format: :short)
|
||||||
|
|
||||||
|
.actions = render('shared/submit_button', f: f, object: @proxy_exercise)
|
3
app/views/proxy_exercises/edit.html.slim
Normal file
3
app/views/proxy_exercises/edit.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = t('activerecord.models.proxy_exercise.one', model: ProxyExercise.model_name.human)+ ": " + @proxy_exercise.title
|
||||||
|
|
||||||
|
= render('form')
|
35
app/views/proxy_exercises/index.html.slim
Normal file
35
app/views/proxy_exercises/index.html.slim
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
h1 = ProxyExercise.model_name.human(count: 2)
|
||||||
|
|
||||||
|
= render(layout: 'shared/form_filters') do |f|
|
||||||
|
.form-group
|
||||||
|
= f.label(:title_cont, t('activerecord.attributes.proxy_exercise.title'), class: 'sr-only')
|
||||||
|
= f.search_field(:title_cont, class: 'form-control', placeholder: t('activerecord.attributes.proxy_exercise.title'))
|
||||||
|
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = sort_link(@search, :title, t('activerecord.attributes.proxy_exercise.title'))
|
||||||
|
th = "Token"
|
||||||
|
th = t('activerecord.attributes.proxy_exercise.files_count')
|
||||||
|
th colspan=6 = t('shared.actions')
|
||||||
|
tbody
|
||||||
|
- @proxy_exercises.each do |proxy_exercise|
|
||||||
|
tr data-id=proxy_exercise.id
|
||||||
|
td = link_to(proxy_exercise.title,proxy_exercise)
|
||||||
|
td = proxy_exercise.token
|
||||||
|
td = proxy_exercise.count_files
|
||||||
|
td = link_to(t('shared.edit'), edit_proxy_exercise_path(proxy_exercise)) if policy(proxy_exercise).edit?
|
||||||
|
|
||||||
|
td
|
||||||
|
.btn-group
|
||||||
|
button.btn.btn-primary-outline.btn-xs.dropdown-toggle data-toggle="dropdown" type="button" = t('shared.actions_button')
|
||||||
|
span.caret
|
||||||
|
span.sr-only Toggle Dropdown
|
||||||
|
ul.dropdown-menu.pull-right role="menu"
|
||||||
|
li = link_to(t('shared.show'), proxy_exercise) if policy(proxy_exercise).show?
|
||||||
|
li = link_to(t('shared.destroy'), proxy_exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete) if policy(proxy_exercise).destroy?
|
||||||
|
li = link_to(t('.clone'), clone_proxy_exercise_path(proxy_exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post) if policy(proxy_exercise).clone?
|
||||||
|
|
||||||
|
= render('shared/pagination', collection: @proxy_exercises)
|
||||||
|
p = render('shared/new_button', model: ProxyExercise)
|
3
app/views/proxy_exercises/new.html.slim
Normal file
3
app/views/proxy_exercises/new.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = t('shared.new_model', model: ProxyExercise.model_name.human)
|
||||||
|
|
||||||
|
= render('form')
|
3
app/views/proxy_exercises/reload.json.jbuilder
Normal file
3
app/views/proxy_exercises/reload.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
json.set! :files do
|
||||||
|
json.array! @exercise.files.visible, :content, :id
|
||||||
|
end
|
23
app/views/proxy_exercises/show.html.slim
Normal file
23
app/views/proxy_exercises/show.html.slim
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
- content_for :head do
|
||||||
|
= javascript_include_tag('http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js')
|
||||||
|
= stylesheet_link_tag('http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css')
|
||||||
|
|
||||||
|
h1
|
||||||
|
= @proxy_exercise.title
|
||||||
|
- if policy(@proxy_exercise).edit?
|
||||||
|
= render('shared/edit_button', object: @proxy_exercise)
|
||||||
|
|
||||||
|
= row(label: 'exercise.title', value: @proxy_exercise.title)
|
||||||
|
= row(label: 'proxy_exercise.files_count', value: @exercises.count)
|
||||||
|
= row(label: 'exercise.description', value: @proxy_exercise.description)
|
||||||
|
h3 Exercises
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = sort_link(@search, :title, t('activerecord.attributes.submission.exercise'))
|
||||||
|
th = sort_link(@search, :created_at, t('shared.created_at'))
|
||||||
|
- @proxy_exercise.exercises.each do |exercise|
|
||||||
|
tr
|
||||||
|
td = link_to(exercise.title, exercise)
|
||||||
|
td = l(exercise.created_at, format: :short)
|
6
app/views/tags/_form.html.slim
Normal file
6
app/views/tags/_form.html.slim
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
= form_for(@tag) do |f|
|
||||||
|
= render('shared/form_errors', object: @tag)
|
||||||
|
.form-group
|
||||||
|
= f.label(:name)
|
||||||
|
= f.text_field(:name, class: 'form-control', required: true)
|
||||||
|
.actions = render('shared/submit_button', f: f, object: @tag)
|
3
app/views/tags/edit.html.slim
Normal file
3
app/views/tags/edit.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = @tag.name
|
||||||
|
|
||||||
|
= render('form')
|
19
app/views/tags/index.html.slim
Normal file
19
app/views/tags/index.html.slim
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
h1 = Tag.model_name.human(count: 2)
|
||||||
|
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = t('activerecord.attributes.hint.name')
|
||||||
|
/th = t('activerecord.attributes.hint.locale')
|
||||||
|
/th colspan=3 = t('shared.actions')
|
||||||
|
tbody
|
||||||
|
- @tags.each do |tag|
|
||||||
|
tr
|
||||||
|
td = tag.name
|
||||||
|
td = link_to(t('shared.show'), tag)
|
||||||
|
td = link_to(t('shared.edit'), edit_tag_path(tag))
|
||||||
|
td = link_to(t('shared.destroy'), tag, data: {confirm: t('shared.confirm_destroy')}, method: :delete) if tag.can_be_destroyed?
|
||||||
|
|
||||||
|
= render('shared/pagination', collection: @tags)
|
||||||
|
p = render('shared/new_button', model: Tag, path: new_tag_path)
|
3
app/views/tags/new.html.slim
Normal file
3
app/views/tags/new.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = t('shared.new_model', model: Hint.model_name.human)
|
||||||
|
|
||||||
|
= render('form')
|
6
app/views/tags/show.html.slim
Normal file
6
app/views/tags/show.html.slim
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
h1
|
||||||
|
= @tag.name
|
||||||
|
= render('shared/edit_button', object: @tag)
|
||||||
|
|
||||||
|
= row(label: 'tag.name', value: @tag.name)
|
||||||
|
= row(label: 'tag.usage', value: @tag.exercises.count)
|
@ -27,6 +27,7 @@ de:
|
|||||||
exercise:
|
exercise:
|
||||||
description: Beschreibung
|
description: Beschreibung
|
||||||
embedding_parameters: Parameter für LTI-Einbettung
|
embedding_parameters: Parameter für LTI-Einbettung
|
||||||
|
tags: Tags
|
||||||
execution_environment: Ausführungsumgebung
|
execution_environment: Ausführungsumgebung
|
||||||
execution_environment_id: Ausführungsumgebung
|
execution_environment_id: Ausführungsumgebung
|
||||||
files: Dateien
|
files: Dateien
|
||||||
@ -34,10 +35,16 @@ de:
|
|||||||
instructions: Anweisungen
|
instructions: Anweisungen
|
||||||
maximum_score: Erreichbare Punktzahl
|
maximum_score: Erreichbare Punktzahl
|
||||||
public: Öffentlich
|
public: Öffentlich
|
||||||
|
selection: Ausgewählt
|
||||||
title: Titel
|
title: Titel
|
||||||
user: Autor
|
user: Autor
|
||||||
allow_auto_completion: "Autovervollständigung aktivieren"
|
allow_auto_completion: "Autovervollständigung aktivieren"
|
||||||
allow_file_creation: "Dateierstellung erlauben"
|
allow_file_creation: "Dateierstellung erlauben"
|
||||||
|
difficulty: Schwierigkeitsgrad
|
||||||
|
worktime: "vermutete Arbeitszeit in Minuten"
|
||||||
|
proxy_exercise:
|
||||||
|
title: Title
|
||||||
|
files_count: Anzahl der Aufgaben
|
||||||
external_user:
|
external_user:
|
||||||
consumer: Konsument
|
consumer: Konsument
|
||||||
email: E-Mail
|
email: E-Mail
|
||||||
@ -91,6 +98,10 @@ de:
|
|||||||
files: Dateien
|
files: Dateien
|
||||||
score: Punktzahl
|
score: Punktzahl
|
||||||
user: Autor
|
user: Autor
|
||||||
|
tag:
|
||||||
|
name: Name
|
||||||
|
usage: Verwendet
|
||||||
|
difficulty: Anteil an der Aufgabe
|
||||||
file_template:
|
file_template:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
file_type: "Dateityp"
|
file_type: "Dateityp"
|
||||||
@ -111,6 +122,9 @@ de:
|
|||||||
exercise:
|
exercise:
|
||||||
one: Aufgabe
|
one: Aufgabe
|
||||||
other: Aufgaben
|
other: Aufgaben
|
||||||
|
proxy_exercise:
|
||||||
|
one: Proxy Aufgabe
|
||||||
|
other: Proxy Aufgaben
|
||||||
external_user:
|
external_user:
|
||||||
one: Externer Nutzer
|
one: Externer Nutzer
|
||||||
other: Externe Nutzer
|
other: Externe Nutzer
|
||||||
@ -290,6 +304,9 @@ de:
|
|||||||
tests: Unit Tests
|
tests: Unit Tests
|
||||||
time_difference: 'Arbeitszeit bis hier*'
|
time_difference: 'Arbeitszeit bis hier*'
|
||||||
addendum: '* Differenzen von mehr als 30 Minuten werden ignoriert.'
|
addendum: '* Differenzen von mehr als 30 Minuten werden ignoriert.'
|
||||||
|
proxy_exercises:
|
||||||
|
index:
|
||||||
|
clone: Duplizieren
|
||||||
external_users:
|
external_users:
|
||||||
statistics:
|
statistics:
|
||||||
title: Statistiken für Externe Benutzer
|
title: Statistiken für Externe Benutzer
|
||||||
|
@ -48,6 +48,7 @@ en:
|
|||||||
exercise:
|
exercise:
|
||||||
description: Description
|
description: Description
|
||||||
embedding_parameters: LTI Embedding Parameters
|
embedding_parameters: LTI Embedding Parameters
|
||||||
|
tags: Tags
|
||||||
execution_environment: Execution Environment
|
execution_environment: Execution Environment
|
||||||
execution_environment_id: Execution Environment
|
execution_environment_id: Execution Environment
|
||||||
files: Files
|
files: Files
|
||||||
@ -55,10 +56,16 @@ en:
|
|||||||
instructions: Instructions
|
instructions: Instructions
|
||||||
maximum_score: Maximum Score
|
maximum_score: Maximum Score
|
||||||
public: Public
|
public: Public
|
||||||
|
selection: Selected
|
||||||
title: Title
|
title: Title
|
||||||
user: Author
|
user: Author
|
||||||
allow_auto_completion: "Allow auto completion"
|
allow_auto_completion: "Allow auto completion"
|
||||||
allow_file_creation: "Allow file creation"
|
allow_file_creation: "Allow file creation"
|
||||||
|
difficulty: Difficulty
|
||||||
|
worktime: "Expected worktime in minutes"
|
||||||
|
proxy_exercise:
|
||||||
|
title: Title
|
||||||
|
files_count: Exercises Count
|
||||||
external_user:
|
external_user:
|
||||||
consumer: Consumer
|
consumer: Consumer
|
||||||
email: Email
|
email: Email
|
||||||
@ -112,6 +119,10 @@ en:
|
|||||||
files: Files
|
files: Files
|
||||||
score: Score
|
score: Score
|
||||||
user: Author
|
user: Author
|
||||||
|
tag:
|
||||||
|
name: Name
|
||||||
|
usage: Used
|
||||||
|
difficulty: Share on the Exercise
|
||||||
file_template:
|
file_template:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
file_type: "File Type"
|
file_type: "File Type"
|
||||||
@ -132,6 +143,9 @@ en:
|
|||||||
exercise:
|
exercise:
|
||||||
one: Exercise
|
one: Exercise
|
||||||
other: Exercises
|
other: Exercises
|
||||||
|
proxy_exercise:
|
||||||
|
one: Proxy Exercise
|
||||||
|
other: Proxy Exercises
|
||||||
external_user:
|
external_user:
|
||||||
one: External User
|
one: External User
|
||||||
other: External Users
|
other: External Users
|
||||||
@ -311,6 +325,9 @@ en:
|
|||||||
tests: Unit Test Results
|
tests: Unit Test Results
|
||||||
time_difference: 'Working Time until here*'
|
time_difference: 'Working Time until here*'
|
||||||
addendum: '* Deltas longer than 30 minutes are ignored.'
|
addendum: '* Deltas longer than 30 minutes are ignored.'
|
||||||
|
proxy_exercises:
|
||||||
|
index:
|
||||||
|
clone: Duplicate
|
||||||
external_users:
|
external_users:
|
||||||
statistics:
|
statistics:
|
||||||
title: External User Statistics
|
title: External User Statistics
|
||||||
|
@ -60,12 +60,29 @@ Rails.application.routes.draw do
|
|||||||
member do
|
member do
|
||||||
post :clone
|
post :clone
|
||||||
get :implement
|
get :implement
|
||||||
|
get :working_times
|
||||||
get :statistics
|
get :statistics
|
||||||
get :reload
|
get :reload
|
||||||
post :submit
|
post :submit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :proxy_exercises do
|
||||||
|
member do
|
||||||
|
post :clone
|
||||||
|
get :reload
|
||||||
|
post :submit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
resources :tags do
|
||||||
|
member do
|
||||||
|
post :clone
|
||||||
|
get :reload
|
||||||
|
post :submit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :external_users, only: [:index, :show], concerns: :statistics do
|
resources :external_users, only: [:index, :show], concerns: :statistics do
|
||||||
resources :exercises, concerns: :statistics
|
resources :exercises, concerns: :statistics
|
||||||
end
|
end
|
||||||
|
14
db/migrate/20170205163247_create_exercise_collections.rb
Normal file
14
db/migrate/20170205163247_create_exercise_collections.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class CreateExerciseCollections < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :exercise_collections do |t|
|
||||||
|
t.string :name
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :exercise_collections_exercises, id: false do |t|
|
||||||
|
t.belongs_to :exercise_collection, index: true
|
||||||
|
t.belongs_to :exercise, index: true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
23
db/migrate/20170205165450_create_proxy_exercises.rb
Normal file
23
db/migrate/20170205165450_create_proxy_exercises.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
class CreateProxyExercises < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :proxy_exercises do |t|
|
||||||
|
t.string :title
|
||||||
|
t.string :description
|
||||||
|
t.string :token
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :exercises_proxy_exercises, id: false do |t|
|
||||||
|
t.belongs_to :proxy_exercise, index: true
|
||||||
|
t.belongs_to :exercise, index: true
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :user_proxy_exercise_exercises do |t|
|
||||||
|
t.belongs_to :user, polymorphic: true, index: true
|
||||||
|
t.belongs_to :proxy_exercise, index: true
|
||||||
|
t.belongs_to :exercise, index: true
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
db/migrate/20170205210357_create_interventions.rb
Normal file
16
db/migrate/20170205210357_create_interventions.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class CreateInterventions < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :user_exercise_interventions do |t|
|
||||||
|
t.belongs_to :user, polymorphic: true
|
||||||
|
t.belongs_to :exercise
|
||||||
|
t.belongs_to :intervention
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :interventions do |t|
|
||||||
|
t.string :name
|
||||||
|
t.text :markup
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
19
db/migrate/20170206141210_add_tags.rb
Normal file
19
db/migrate/20170206141210_add_tags.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class AddTags < ActiveRecord::Migration
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :exercises, :expected_worktime_seconds, :integer, default: 0
|
||||||
|
add_column :exercises, :expected_difficulty, :integer, default: 1
|
||||||
|
|
||||||
|
create_table :tags do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :exercise_tags do |t|
|
||||||
|
t.belongs_to :exercise
|
||||||
|
t.belongs_to :tag
|
||||||
|
t.integer :factor, default: 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
11
db/migrate/20170206152503_add_user_feedback.rb
Normal file
11
db/migrate/20170206152503_add_user_feedback.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class AddUserFeedback < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :user_exercise_feedbacks do |t|
|
||||||
|
t.belongs_to :exercise, null: false
|
||||||
|
t.belongs_to :user, polymorphic: true, null: false
|
||||||
|
t.integer :difficulty
|
||||||
|
t.integer :working_time_seconds
|
||||||
|
t.string :feedback_text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Reference in New Issue
Block a user