Skip to content

Commit

Permalink
Assessments (#34)
Browse files Browse the repository at this point in the history
* Added Question and Assessment

* Added functionality for grading

* Added assessments functionality

* Fixed typo

* Restructured assessments

* Added assessments context

* Added controllers

* Delete duplicate file

* Changes to suit convention

* Fixed stuff

* Ran mix commands

* Fix changes

* Removed role validation

* Removed all compile time errors

* Fixed consistency issues

* Refactored

* Conversion to spaces

* More functionality for questions

* Adding tests

* Refactored

* Added few tests for missions

* Few more tests to go

* Added more tests

* Delete useless migration

* Delete useless migration 2

* Added submission test

* Added question tests and switched controllers to new branch

* Added answers test

* Remove unneccesary stuff

* Formatting

* Fixed compile error

* Make changes according to review

* Fixes

* Added tests to question and answer

* Added question types

* Various fixes

* Changes according to question types

* Assessments context

* Fixes

* Removed invalid test

* Fix some tests

* Assessments housecleaning (#51)

* Improve contexts

- Allow using multiple contexts
- Cleaned up some imports in /lib/cadet/accessments

* Raise exception when using context with invalid arguments

* More housecleaning

* Add comment to cadet context

* Fix credo

* Remove duplicate blank line

* Remove pre-commit

* Refactor json and rename Mission to Assessment

* Fix submission model

* Use cadet context

* Fix some json tests

* Fix tests and logic error

* Remove untested method and add one test

* Remove useless import

* Add test

* Fix according to code review

* Change to pipeline syntax

* Reorganise file
  • Loading branch information
sreycodes authored and indocomsoft committed Jun 24, 2018
1 parent de7d5b9 commit cb56954
Show file tree
Hide file tree
Showing 37 changed files with 933 additions and 86 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ erl_crash.dump
.terraform
*.tfstate
*.backup

2 changes: 0 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,3 @@ config :guardian, Guardian.DB,
token_types: ["access"],
# default: 60 minute
sweep_interval: 60

config :pre_commit, commands: ["format --check-formatted", "test", "credo"]
61 changes: 59 additions & 2 deletions lib/cadet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Cadet do
use Ecto.Schema

import Ecto.Changeset
import Cadet.ModelHelper
end
end

Expand All @@ -19,10 +20,66 @@ defmodule Cadet do
alias Cadet.Repo

import Ecto.Changeset
import Cadet.ContextHelper
end
end

defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
def display do
quote do
import Cadet.DisplayHelper
end
end

def remote_assets do
quote do
use Arc.Definition
use Arc.Ecto.Definition
end
end

defp apply_single(context) do
apply(__MODULE__, context, [])
end

defp apply_multiple(contexts) do
contexts
|> Enum.filter(&is_atom/1)
|> Enum.map(&apply_single/1)
|> join_context_quotes
end

defp join_context_quotes(context_quotes) do
Enum.reduce(
context_quotes,
quote do
end,
fn context, acc ->
quote do
unquote(acc)
unquote(context)
end
end
)
end

@doc """
The `use Cadet` macro supports both single and multiple arguments i.e.:
```
use Cadet, :context
use Cadet, [:context, :model, :etc]
```
"""
defmacro __using__(opt) do
cond do
is_atom(opt) ->
apply_single(opt)

is_list(opt) ->
apply_multiple(opt)

true ->
raise "invalid arguments when using Cadet contexts"
end
end
end
32 changes: 32 additions & 0 deletions lib/cadet/assessments/answer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Cadet.Assessments.Answer do
@moduledoc """
Answers model contains domain logic for answers management for
programming and multiple choice questions.
"""
use Cadet, :model

alias Cadet.Assessments.ProblemType
alias Cadet.Assessments.Submission
alias Cadet.Assessments.Question

schema "answers" do
field(:marks, :float, default: 0.0)
field(:answer, :map)
field(:type, ProblemType)
field(:raw_answer, :string, virtual: true)
belongs_to(:submission, Submission)
belongs_to(:question, Question)
timestamps()
end

@required_fields ~w(answer type)a
@optional_fields ~w(marks raw_answer)a

def changeset(answer, params) do
answer
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_number(:marks, greater_than_or_equal_to: 0.0)
|> put_json(:answer, :raw_answer)
end
end
5 changes: 1 addition & 4 deletions lib/cadet/assessments/answer_types/mcq_answer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ defmodule Cadet.Assessments.AnswerTypes.MCQAnswer do
The Assessments.QuestionTypes.MCQQuestion entity represents an MCQ Answer.
It comprises of one of the MCQ choices.
"""
use Ecto.Schema

import Ecto.Changeset
# TODO: use Cadet context after !34 is merged
use Cadet, :model

embedded_schema do
field(:choice_id, :integer)
Expand Down
4 changes: 1 addition & 3 deletions lib/cadet/assessments/answer_types/programming_answer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ defmodule Cadet.Assessments.AnswerTypes.ProgrammingAnswer do
@moduledoc """
The ProgrammingQuestion entity represents a Programming question.
"""
use Ecto.Schema

import Ecto.Changeset
use Cadet, :model

embedded_schema do
field(:code, :string)
Expand Down
57 changes: 57 additions & 0 deletions lib/cadet/assessments/assessment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule Cadet.Assessments.Assessment do
@moduledoc """
The Assessment entity stores metadata of a students' assessment
(mission, sidequest, path, and contest)
"""
use Cadet, :model
use Arc.Ecto.Schema

alias Cadet.Assessments.Category
alias Cadet.Assessments.Image
alias Cadet.Assessments.Question
alias Cadet.Assessments.Upload

schema "assessments" do
field(:title, :string)
field(:is_published, :boolean, default: false)
field(:category, Category)
field(:summary_short, :string)
field(:summary_long, :string)
field(:open_at, Timex.Ecto.DateTime)
field(:close_at, Timex.Ecto.DateTime)
field(:max_xp, :integer, default: 0)
field(:cover_picture, Image.Type)
field(:mission_pdf, Upload.Type)
field(:order, :string, default: "")
has_many(:questions, Question, on_delete: :delete_all)
timestamps()
end

@required_fields ~w(category title open_at close_at max_xp)a
@optional_fields ~w(summary_short summary_long is_published max_xp)a
@optional_file_fields ~w(cover_picture mission_pdf)a

def changeset(mission, params) do
params =
params
|> convert_date(:open_at)
|> convert_date(:close_at)

mission
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_number(:max_xp, greater_than_or_equal_to: 0)
|> cast_attachments(params, @optional_file_fields)
|> validate_open_close_date
end

defp validate_open_close_date(changeset) do
validate_change(changeset, :open_at, fn :open_at, open_at ->
if Timex.before?(open_at, get_field(changeset, :close_at)) do
[]
else
[open_at: "Open date must be before close date"]
end
end)
end
end
131 changes: 119 additions & 12 deletions lib/cadet/assessments/assessments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,136 @@ defmodule Cadet.Assessments do
Assessments context contains domain logic for assessments management such as
missions, sidequests, paths, etc.
"""
use Cadet, :context
use Cadet, [:context, :display]

alias Cadet.Assessments.QuestionTypes.MCQQuestion
alias Cadet.Assessments.QuestionTypes.ProgrammingQuestion
import Ecto.Query

# To be uncommented when assessments context is merged
alias Timex.Duration

alias Cadet.Assessments.Assessment
alias Cadet.Assessments.Question

def all_assessments() do
Repo.all(Assessment)
end

def all_assessments(category) do
Repo.all(from(a in Assessment, where: a.category == ^category))
end

def all_open_assessments(category) do
now = Timex.now()

assessment_with_category = Repo.all(from(a in Assessment, where: a.category == ^category))
# TODO: Refactor to be done on SQL instead of in-memory
Enum.filter(assessment_with_category, &(&1.is_published and Timex.before?(&1.open_at, now)))
end

def assessments_due_soon() do
now = Timex.now()
week_after = Timex.add(now, Duration.from_weeks(1))

all_assessments()
|> Enum.filter(
&(&1.is_published and Timex.before?(&1.open_at, now) and
Timex.between?(&1.close_at, now, week_after))
)
end

def build_assessment(params) do
Assessment.changeset(%Assessment{}, params)
end

def build_question(params) do
Question.changeset(%Question{}, params)
end

def create_assessment(params) do
params
|> build_assessment
|> Repo.insert()
end

def update_assessment(id, params) do
simple_update(
Assessment,
id,
using: &Assessment.changeset/2,
params: params
)
end

def update_question(id, params) do
simple_update(
Question,
id,
using: &Question.changeset/2,
params: params
)
end

def publish_assessment(id) do
id
|> get_assessment()
|> change(%{is_published: true})
|> Repo.update()
end

def get_question(id) do
Repo.get(Question, id)
end

def get_assessment(id) do
Repo.get(Assessment, id)
end

def create_question_for_assessment(params, assessment_id)
when is_binary(assessment_id) or is_number(assessment_id) do
assessment = get_assessment(assessment_id)
create_question_for_assessment(params, assessment)
end

def create_question_for_assessment(params, assessment) do
Repo.transaction(fn ->
assessment = Repo.preload(assessment, :questions)
questions = assessment.questions

changeset =
params
|> build_question
|> put_assoc(:assessment, assessment)
|> put_display_order(questions)

case Repo.insert(changeset) do
{:ok, question} -> question
{:error, changeset} -> Repo.rollback(changeset)
end
end)
end

def delete_question(id) do
question = Repo.get(Question, id)
Repo.delete(question)
end

# TODO: Decide what to do with these methods
# def create_multiple_choice_question(json_attr) when is_binary(json_attr) do
# %MCQQuestion{}
# |> MCQQuestion.changeset(%{raw_mcqquestion: json_attr})
# %MCQQuestion{}
# |> MCQQuestion.changeset(%{raw_mcqquestion: json_attr})
# end

# def create_multiple_choice_question(attr = %{}) do
# %MCQQuestion{}
# |> MCQQuestion.changeset(attr)
# %MCQQuestion{}
# |> MCQQuestion.changeset(attr)
# end

# def create_programming_question(json_attr) when is_binary(json_attr) do
# %ProgrammingQuestion{}
# |> ProgrammingQuestion.changeset(%{raw_programmingquestion: json_attr})
# %ProgrammingQuestion{}
# |> ProgrammingQuestion.changeset(%{raw_programmingquestion: json_attr})
# end

# def create_programming_question(attr = %{}) do
# %ProgrammingQuestion{}
# |> ProgrammingQuestion.changeset(attr)
# %ProgrammingQuestion{}
# |> ProgrammingQuestion.changeset(attr)
# end
end
3 changes: 1 addition & 2 deletions lib/cadet/assessments/image.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ defmodule Cadet.Assessments.Image do
@moduledoc """
Image assets used by the missions
"""
use Arc.Definition
use Arc.Ecto.Definition
use Cadet, :remote_assets

@versions [:original]

Expand Down
26 changes: 0 additions & 26 deletions lib/cadet/assessments/mission.ex

This file was deleted.

6 changes: 6 additions & 0 deletions lib/cadet/assessments/problem_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import EctoEnum

defenum(Cadet.Assessments.ProblemType, :type, [
:programming,
:multiple_choice
])
Loading

0 comments on commit cb56954

Please sign in to comment.