transferred Code Ocean from original repository to GitHub

This commit is contained in:
Hauke Klement
2015-01-22 09:51:49 +01:00
commit 4cbf9970b1
683 changed files with 11979 additions and 0 deletions

0
app/models/.keep Normal file
View File

View File

@@ -0,0 +1,84 @@
require File.expand_path('../../../uploaders/file_uploader', __FILE__)
module CodeOcean
class File < ActiveRecord::Base
DEFAULT_WEIGHT = 1.0
ROLES = %w[main_file reference_implementation regular_file teacher_defined_test user_defined_file user_defined_test]
TEACHER_DEFINED_ROLES = ROLES - %w[user_defined_file]
after_initialize :set_default_values
before_validation :set_ancestor_values, if: :incomplete_descendent?
before_validation :hash_content, if: :content_present?
belongs_to :context, polymorphic: true
belongs_to :execution_environment
belongs_to :file
alias_method :ancestor, :file
belongs_to :file_type
has_many :files
alias_method :descendants, :files
mount_uploader :native_file, FileUploader
scope :editable, -> { where(read_only: false) }
scope :visible, -> { where(hidden: false) }
validates :feedback_message, if: :teacher_defined_test?, presence: true
validates :feedback_message, absence: true, unless: :teacher_defined_test?
validates :file_type_id, presence: true
validates :hashed_content, if: :content_present?, presence: true
validates :hidden, inclusion: {in: [true, false]}
validates :name, presence: true
validates :read_only, inclusion: {in: [true, false]}
validates :role, inclusion: {in: ROLES}
validates :weight, if: :teacher_defined_test?, numericality: true, presence: true
validates :weight, absence: true, unless: :teacher_defined_test?
ROLES.each do |role|
define_method("#{role}?") { self.role == role }
end
def ancestor_id
file_id || id
end
def content_present?
content? || native_file?
end
private :content_present?
def hash_content
self.hashed_content = Digest::MD5.new.hexdigest(file_type.binary? ? ::File.new(native_file.file.path, 'r').read : content)
end
private :hash_content
def incomplete_descendent?
file_id.present? && file_type_id.blank?
end
private :incomplete_descendent?
def name_with_extension
name + (file_type.file_extension || '')
end
def set_ancestor_values
[:feedback_message, :file_type_id, :hidden, :name, :path, :read_only, :role, :weight].each do |attribute|
send(:"#{attribute}=", ancestor.send(attribute))
end
end
private :set_ancestor_values
def set_default_values
self.content ||= ''
self.hidden ||= false
self.read_only ||= false
self.weight ||= DEFAULT_WEIGHT if teacher_defined_test?
end
private :set_default_values
def visible
!hidden
end
end
end

View File

View File

@@ -0,0 +1,20 @@
module Context
extend ActiveSupport::Concern
included do
has_many :files, as: :context, class: CodeOcean::File
accepts_nested_attributes_for :files
end
def add_file(file_attributes)
file = files.create(file_attributes)
save
file
end
def add_file!(file_attributes)
file = files.create!(file_attributes)
save!
file
end
end

View File

@@ -0,0 +1,12 @@
module Creation
extend ActiveSupport::Concern
included do
belongs_to :user, polymorphic: true
alias_method :author, :user
alias_method :creator, :user
validates :user_id, presence: true
validates :user_type, presence: true
end
end

View File

@@ -0,0 +1,30 @@
module User
extend ActiveSupport::Concern
ROLES = %w[admin teacher]
included do
belongs_to :consumer
has_many :exercises, as: :user
has_many :file_types, as: :user
has_many :submissions, as: :user
scope :with_submissions, -> { where('id IN (SELECT user_id FROM submissions)') }
end
ROLES.each do |role|
define_method("#{role}?") { self.try(:role) == role }
end
def external?
is_a?(ExternalUser)
end
def internal?
is_a?(InternalUser)
end
def to_s
name
end
end

13
app/models/consumer.rb Normal file
View File

@@ -0,0 +1,13 @@
class Consumer < ActiveRecord::Base
has_many :users
scope :with_users, -> { where('id IN (SELECT consumer_id FROM internal_users)') }
validates :name, presence: true
validates :oauth_key, presence: true, uniqueness: true
validates :oauth_secret, presence: true
def to_s
name
end
end

14
app/models/error.rb Normal file
View File

@@ -0,0 +1,14 @@
class Error < ActiveRecord::Base
belongs_to :execution_environment
scope :for_execution_environment, ->(execution_environment) do
Error.find_by_sql("SELECT MAX(created_at) AS created_at, MAX(id) AS id, message, COUNT(*) AS count FROM errors WHERE #{sanitize_sql_hash_for_conditions(execution_environment_id: execution_environment.id)} GROUP BY message ORDER BY count DESC")
end
validates :execution_environment_id, presence: true
validates :message, presence: true
def self.nested_resource?
true
end
end

View File

@@ -0,0 +1,42 @@
class ExecutionEnvironment < ActiveRecord::Base
include Creation
VALIDATION_COMMAND = 'whoami'
has_many :exercises
has_many :hints
scope :with_exercises, -> { where('id IN (SELECT execution_environment_id FROM exercises)') }
validate :valid_test_setup?
validate :working_docker_image?, if: :validate_docker_image?
validates :docker_image, presence: true
validates :name, presence: true
validates :permitted_execution_time, numericality: {only_integer: true}, presence: true
validates :run_command, presence: true
def to_s
name
end
def valid_test_setup?
if test_command? ^ testing_framework?
errors.add(:test_command, I18n.t('activerecord.errors.messages.together', attribute: I18n.t('activerecord.attributes.execution_environment.testing_framework')))
end
end
private :valid_test_setup?
def validate_docker_image?
docker_image.present? && Rails.env != 'test'
end
private :validate_docker_image?
def working_docker_image?
DockerClient.pull(docker_image) unless DockerClient.image_tags.include?(docker_image)
output = DockerClient.new(execution_environment: self).execute_command(VALIDATION_COMMAND)
errors.add(:docker_image, "error: #{output[:stderr]}") if output[:stderr].present?
rescue DockerClient::Error => error
errors.add(:docker_image, "error: #{error}")
end
private :working_docker_image?
end

60
app/models/exercise.rb Normal file
View File

@@ -0,0 +1,60 @@
class Exercise < ActiveRecord::Base
include Context
include Creation
after_initialize :generate_token
after_initialize :set_default_values
belongs_to :execution_environment
has_many :submissions
has_many :users, source_type: ExternalUser, through: :submissions
scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') }
validate :valid_main_file?
validates :description, presence: true
validates :execution_environment_id, presence: true
validates :public, inclusion: {in: [true, false]}
validates :title, presence: true
validates :token, presence: true, uniqueness: true
def average_percentage
(average_score / maximum_score * 100).round if average_score
end
def average_score
ActiveRecord::Base.connection.execute("SELECT AVG(score) AS average_score FROM (SELECT MAX(score) AS score FROM submissions WHERE cause = 'submit' AND exercise_id = '#{id}' GROUP BY user_id) AS maximum_scores")[0]['average_score'].to_f.round(2)
end
def duplicate(attributes)
exercise = dup
exercise.attributes = attributes
files.each { |file| exercise.files << file.dup }
exercise
end
def generate_token
self.token ||= SecureRandom.hex(4)
end
private :generate_token
def maximum_score
files.where(role: 'teacher_defined_test').sum(:weight)
end
def set_default_values
self.public ||= false
end
private :set_default_values
def to_s
title
end
def valid_main_file?
if files.where(role: 'main_file').count > 1
errors.add(:files, I18n.t('activerecord.errors.models.exercise.at_most_one_main_file'))
end
end
private :valid_main_file?
end

View File

@@ -0,0 +1,6 @@
class ExternalUser < ActiveRecord::Base
include User
validates :consumer_id, presence: true
validates :external_id, presence: true
end

35
app/models/file_type.rb Normal file
View File

@@ -0,0 +1,35 @@
class FileType < ActiveRecord::Base
include Creation
AUDIO_FILE_EXTENSIONS = %w[.aac .flac .m4a .mp3 .ogg .wav .wma]
IMAGE_FILE_EXTENSIONS = %w[.bmp .gif .jpeg .jpg .png]
VIDEO_FILE_EXTENSIONS = %w[.avi .flv .mkv .mp4 .m4v .ogv .webm]
after_initialize :set_default_values
has_many :files
validates :binary, inclusion: {in: [true, false]}
validates :editor_mode, presence: true, unless: :binary?
validates :executable, inclusion: {in: [true, false]}
validates :indent_size, presence: true, unless: :binary?
validates :name, presence: true
validates :renderable, inclusion: {in: [true, false]}
[:audio, :image, :video].each do |type|
define_method("#{type}?") do
self.class.const_get("#{type.upcase}_FILE_EXTENSIONS").include?(file_extension)
end
end
def set_default_values
self.binary ||= false
self.executable ||= false
self.renderable ||= false
end
private :set_default_values
def to_s
name
end
end

17
app/models/hint.rb Normal file
View File

@@ -0,0 +1,17 @@
class Hint < ActiveRecord::Base
belongs_to :execution_environment
validates :execution_environment_id, presence: true
validates :locale, presence: true
validates :message, presence: true
validates :name, presence: true
validates :regular_expression, presence: true
def self.nested_resource?
true
end
def to_s
name
end
end

View File

@@ -0,0 +1,13 @@
class InternalUser < ActiveRecord::Base
include User
authenticates_with_sorcery!
validates :email, presence: true, uniqueness: true
validates :password, confirmation: true, on: :update, presence: true, unless: :activated?
validates :role, inclusion: {in: ROLES}
def activated?
activation_state == 'active'
end
end

58
app/models/submission.rb Normal file
View File

@@ -0,0 +1,58 @@
class Submission < ActiveRecord::Base
include Context
include Creation
CAUSES = %w[assess download file render run save submit test]
FILENAME_URL_PLACEHOLDER = '{filename}'
belongs_to :exercise
scope :final, -> { where(cause: 'submit') }
scope :intermediate, -> { where.not(cause: 'submit') }
validates :cause, inclusion: {in: CAUSES}
validates :exercise_id, presence: true
def collect_files
ancestors = exercise.files.map(&:id).zip(exercise.files)
descendants = files.map(&:file_id).zip(files)
(ancestors + descendants).to_h.values
end
def execution_environment
exercise.execution_environment
end
[:download, :render, :run, :test].each do |action|
filename = FILENAME_URL_PLACEHOLDER.gsub(/\W/, '')
define_method("#{action}_url") do
Rails.application.routes.url_helpers.send(:"#{action}_submission_path", self, filename).sub(filename, FILENAME_URL_PLACEHOLDER)
end
end
def main_file
collect_files.detect(&:main_file?)
end
def normalized_score
score / exercise.maximum_score
end
def percentage
(normalized_score * 100).round
end
[:score, :stop].each do |action|
define_method("#{action}_url") do
Rails.application.routes.url_helpers.send(:"#{action}_submission_path", self)
end
end
def siblings
Submission.where(exercise_id: exercise_id, user_id: user_id, user_type: user_type)
end
def to_s
Submission.model_name.human
end
end