Skip to content

Commit

Permalink
Merge pull request #35 from jeferson-sb/spaced-repetition
Browse files Browse the repository at this point in the history
feat: spaced repetition
  • Loading branch information
jeferson-sb authored Feb 28, 2024
2 parents c62bf9a + c3ae3ec commit e895f1e
Show file tree
Hide file tree
Showing 19 changed files with 324 additions and 98 deletions.
1 change: 1 addition & 0 deletions api/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ group :development, :test do
gem 'rubocop-rails', '~> 2.19', require: false
gem 'rubocop-rspec', '~> 2.22', require: false
gem 'shoulda-matchers', '~> 5.1'
gem 'whenever', '~> 1.0', require: false
end

group :development do
Expand Down
4 changes: 4 additions & 0 deletions api/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ GEM
bootsnap (1.16.0)
msgpack (~> 1.2)
builder (3.2.4)
chronic (0.10.2)
concurrent-ruby (1.2.2)
crass (1.0.6)
date (3.3.3)
Expand Down Expand Up @@ -228,6 +229,8 @@ GEM
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whenever (1.0.0)
chronic (>= 0.6.3)
zeitwerk (2.6.7)

PLATFORMS
Expand All @@ -252,6 +255,7 @@ DEPENDENCIES
shoulda-matchers (~> 5.1)
sqlite3 (~> 1.4)
tzinfo-data
whenever (~> 1.0)

RUBY VERSION
ruby 3.2.0p0
Expand Down
11 changes: 10 additions & 1 deletion api/app/controllers/exams_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ def index
render json: @exams
end

# Evaluates an exam
# FindOrCreate revision (if missed questions)
# Store the final answer for the user
def evaluate
@exam = Exam.find(params[:exam_id])
questions = params[:questions]

score, questions_answered_incorrectly = Exams::Evaluate.perform(questions, @exam.questions.length)
Revisions::Create.perform(params[:exam_id], @user.id, questions_answered_incorrectly)
answer = Answer.find_or_create_by(
exam_id: params[:exam_id],
user_id: @user.id,
score:
)
answer.attempt

render json: { score: }, status: :created
end
Expand Down Expand Up @@ -56,7 +65,7 @@ def evaluate_duos
end

private

def create_params
params.permit(:title, :difficulty, :version, :category_id, :question_ids)
end
Expand Down
11 changes: 11 additions & 0 deletions api/app/mailers/notification_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class NotificationMailer < ApplicationMailer
default from: 'noreply@flashmemo.com'

def review_email
@user = params[:user]
@url = params[:url]
mail(to: @user.email, subject: 'Hey, Review time!')
end
end
16 changes: 16 additions & 0 deletions api/app/models/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@ class Answer < ApplicationRecord
belongs_to :user
belongs_to :exam

INTERVALS = {
1 => 3.days,
2 => 1.week,
3 => 1.month
}.freeze

validates :user, presence: true
validates :exam, presence: true
validates :score, presence: true

def attempt
self.last_attempted_at = Time.now
self.interval_level += 1 if score < 100 && interval_level < INTERVALS.keys.max
save
end

def valid_interval?
last_attempted_at + INTERVALS[self.interval_level] < Time.now
end
end
14 changes: 14 additions & 0 deletions api/app/views/notification_mailer/review_email.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Hey <%= @user.username %></h1>
<p>
We bring you these questions to review to <strong>reinforce</strong> your learning!
Just follow <a href=<%= @url %> target="_blank" rel="noopener">this link</a>
</p>
<p>Thanks, have a great day!</p>
</body>
</html>
7 changes: 7 additions & 0 deletions api/app/views/notification_mailer/review_email.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hey <%= @user.username %>
==========================

We bring you these questions to review to improve your learning!
Just follow this link: <%= @url %>

Thanks, have a great day!
2 changes: 1 addition & 1 deletion api/app/views/revisions/show.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ json.user_id @revision.exam_id
json.questions @revision.questions

json.created_at @revision.created_at
json.updated_at @revision.updated_at
json.updated_at @revision.updated_at
9 changes: 9 additions & 0 deletions api/config/schedule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

env :PATH, ENV['PATH']
set :environment, :development
set :output, "#{path}/log/cron.log"

every :day do
rake 'send_review_email:all'
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddLastAttemptedToAnswers < ActiveRecord::Migration[7.0]
def change
add_column :answers, :last_attempted_at, :datetime
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddIntervalLevelToAnswers < ActiveRecord::Migration[7.0]
def change
add_column :answers, :interval_level, :integer, default: 0
end
end
187 changes: 93 additions & 94 deletions api/db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions api/lib/tasks/send_review_email.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace :send_review_email do
desc 'Send review email to answer revision'
task all: :environment do
Revision.find_each(batch_size: 100) do |rev|
answer = rev.exam.answer.last
is_enough_questions = rev.questions.length > 1
if answer && (is_enough_questions && answer.valid_interval?)
puts "[LOG] #{Time.now}: sending e-mail revision #{rev.id}"
NotificationMailer.with(user: rev.user_id, url: "/api/revisions/#{rev.id}").review_email.deliver_now
end
end
end
end
Loading

0 comments on commit e895f1e

Please sign in to comment.