active_recall 1.8.6 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +11 -7
- data/.tool-versions +1 -1
- data/Appraisals +15 -0
- data/Gemfile.lock +169 -9
- data/README.md +14 -4
- data/active_recall.gemspec +5 -3
- data/bin/setup +1 -0
- data/bin/spec +14 -0
- data/gemfiles/rails_6_0.gemfile +7 -0
- data/gemfiles/rails_6_0.gemfile.lock +189 -0
- data/gemfiles/rails_6_1.gemfile +7 -0
- data/gemfiles/rails_6_1.gemfile.lock +189 -0
- data/gemfiles/rails_7_0.gemfile +7 -0
- data/gemfiles/rails_7_0.gemfile.lock +216 -0
- data/gemfiles/rails_7_1.gemfile +7 -0
- data/gemfiles/rails_7_1.gemfile.lock +216 -0
- data/lib/active_recall/algorithms/fibonacci_sequence.rb +9 -0
- data/lib/active_recall/algorithms/leitner_system.rb +10 -1
- data/lib/active_recall/algorithms/sm2.rb +97 -0
- data/lib/active_recall/algorithms/soft_leitner_system.rb +10 -1
- data/lib/active_recall/item_methods.rb +4 -0
- data/lib/active_recall/models/item.rb +23 -3
- data/lib/active_recall/version.rb +1 -1
- data/lib/active_recall.rb +3 -0
- data/lib/generators/active_recall/active_recall_generator.rb +1 -0
- data/lib/generators/active_recall/templates/add_active_recall_item_easiness_factor.rb +11 -0
- metadata +58 -12
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecall
|
4
|
+
class SM2
|
5
|
+
MIN_EASINESS_FACTOR = 1.3
|
6
|
+
|
7
|
+
def self.required_attributes
|
8
|
+
REQUIRED_ATTRIBUTES
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.score(box:, easiness_factor:, times_right:, times_wrong:, grade:, current_time: Time.current)
|
12
|
+
new(
|
13
|
+
box: box,
|
14
|
+
easiness_factor: easiness_factor,
|
15
|
+
times_right: times_right,
|
16
|
+
times_wrong: times_wrong,
|
17
|
+
grade: grade,
|
18
|
+
current_time: current_time
|
19
|
+
).score
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.type
|
23
|
+
:gradable
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(box:, easiness_factor:, times_right:, times_wrong:, grade:, current_time: Time.current)
|
27
|
+
@box = box
|
28
|
+
@easiness_factor = easiness_factor || 2.5
|
29
|
+
@times_right = times_right
|
30
|
+
@times_wrong = times_wrong
|
31
|
+
@grade = grade
|
32
|
+
@current_time = current_time
|
33
|
+
@interval = [1, box].max
|
34
|
+
end
|
35
|
+
|
36
|
+
def score
|
37
|
+
raise "Grade must be between 0-5!" unless GRADES.include?(@grade)
|
38
|
+
update_easiness_factor
|
39
|
+
update_repetition_and_interval
|
40
|
+
|
41
|
+
{
|
42
|
+
box: @box,
|
43
|
+
easiness_factor: @easiness_factor,
|
44
|
+
times_right: @times_right,
|
45
|
+
times_wrong: @times_wrong,
|
46
|
+
last_reviewed: @current_time,
|
47
|
+
next_review: next_review
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
GRADES = [
|
54
|
+
5, # Perfect response. The learner recalls the information without hesitation.
|
55
|
+
4, # Correct response after a hesitation. The learner recalls the information but with some difficulty.
|
56
|
+
3, # Correct response recalled with serious difficulty. The learner struggles but eventually recalls the information.
|
57
|
+
2, # Incorrect response, but the learner was very close to the correct answer. This might involve recalling some of the information correctly but not all of it.
|
58
|
+
1, # Incorrect response, but the learner feels they should have remembered it. This is typically used when the learner has a sense of familiarity with the material but fails to recall it correctly.
|
59
|
+
0 # Complete blackout. The learner does not recall the information at all.
|
60
|
+
].freeze
|
61
|
+
REQUIRED_ATTRIBUTES = [
|
62
|
+
:box,
|
63
|
+
:easiness_factor,
|
64
|
+
:grade,
|
65
|
+
:times_right,
|
66
|
+
:times_wrong
|
67
|
+
].freeze
|
68
|
+
|
69
|
+
def update_easiness_factor
|
70
|
+
@easiness_factor += (0.1 - (5 - @grade) * (0.08 + (5 - @grade) * 0.02))
|
71
|
+
@easiness_factor = [@easiness_factor, MIN_EASINESS_FACTOR].max
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_repetition_and_interval
|
75
|
+
if @grade >= 3
|
76
|
+
@box += 1
|
77
|
+
@times_right += 1
|
78
|
+
@interval = case @box
|
79
|
+
when 1
|
80
|
+
1
|
81
|
+
when 2
|
82
|
+
6
|
83
|
+
else
|
84
|
+
(@interval || 1) * @easiness_factor
|
85
|
+
end
|
86
|
+
else
|
87
|
+
@box = 0
|
88
|
+
@times_wrong += 1
|
89
|
+
@interval = 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def next_review
|
94
|
+
@current_time + @interval.days
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
module ActiveRecall
|
4
4
|
class SoftLeitnerSystem
|
5
|
-
|
5
|
+
def self.required_attributes
|
6
|
+
REQUIRED_ATTRIBUTES
|
7
|
+
end
|
6
8
|
|
7
9
|
def self.right(box:, times_right:, times_wrong:, current_time: Time.current)
|
8
10
|
new(
|
@@ -13,6 +15,10 @@ module ActiveRecall
|
|
13
15
|
).right
|
14
16
|
end
|
15
17
|
|
18
|
+
def self.type
|
19
|
+
:binary
|
20
|
+
end
|
21
|
+
|
16
22
|
def self.wrong(box:, times_right:, times_wrong:, current_time: Time.current)
|
17
23
|
new(
|
18
24
|
box: box,
|
@@ -55,6 +61,9 @@ module ActiveRecall
|
|
55
61
|
|
56
62
|
private
|
57
63
|
|
64
|
+
DELAYS = [3, 7, 14, 30, 60, 120, 240].freeze
|
65
|
+
REQUIRED_ATTRIBUTES = [:box, :times_right, :times_wrong].freeze
|
66
|
+
|
58
67
|
attr_accessor :box
|
59
68
|
attr_reader :current_time, :times_right, :times_wrong
|
60
69
|
|
@@ -17,16 +17,34 @@ module ActiveRecall
|
|
17
17
|
where(["box > ? and next_review > ?", 0, current_time])
|
18
18
|
end
|
19
19
|
|
20
|
+
def score!(grade)
|
21
|
+
if algorithm_class.type == :gradable
|
22
|
+
update!(
|
23
|
+
algorithm_class.score(**scoring_attributes.merge(grade: grade))
|
24
|
+
).score
|
25
|
+
else
|
26
|
+
raise IncompatibleAlgorithmError, "#{algorithm_class.name} is a not an gradable algorithm, so is not compatible with the #score! method"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
20
30
|
def source
|
21
31
|
source_type.constantize.find(source_id)
|
22
32
|
end
|
23
33
|
|
24
34
|
def right!
|
25
|
-
|
35
|
+
if algorithm_class.type == :binary
|
36
|
+
update!(algorithm_class.right(**scoring_attributes))
|
37
|
+
else
|
38
|
+
raise IncompatibleAlgorithmError, "#{algorithm_class.name} is not a binary algorithm, so is not compatible with the #right! method"
|
39
|
+
end
|
26
40
|
end
|
27
41
|
|
28
42
|
def wrong!
|
29
|
-
|
43
|
+
if algorithm_class.type == :binary
|
44
|
+
update!(algorithm_class.wrong(**scoring_attributes))
|
45
|
+
else
|
46
|
+
raise IncompatibleAlgorithmError, "#{algorithm_class.name} is not a binary algorithm, so is not compatible with the #wrong! method"
|
47
|
+
end
|
30
48
|
end
|
31
49
|
|
32
50
|
private
|
@@ -36,7 +54,9 @@ module ActiveRecall
|
|
36
54
|
end
|
37
55
|
|
38
56
|
def scoring_attributes
|
39
|
-
attributes
|
57
|
+
attributes
|
58
|
+
.symbolize_keys
|
59
|
+
.slice(*algorithm_class.required_attributes)
|
40
60
|
end
|
41
61
|
end
|
42
62
|
end
|
data/lib/active_recall.rb
CHANGED
@@ -6,6 +6,7 @@ require "active_recall/item_methods"
|
|
6
6
|
require "active_recall/algorithms/fibonacci_sequence"
|
7
7
|
require "active_recall/algorithms/leitner_system"
|
8
8
|
require "active_recall/algorithms/soft_leitner_system"
|
9
|
+
require "active_recall/algorithms/sm2"
|
9
10
|
require "active_recall/configuration"
|
10
11
|
require "active_recall/models/deck"
|
11
12
|
require "active_recall/models/item"
|
@@ -29,4 +30,6 @@ module ActiveRecall
|
|
29
30
|
def self.reset
|
30
31
|
@configuration = Configuration.new
|
31
32
|
end
|
33
|
+
|
34
|
+
class IncompatibleAlgorithmError < StandardError; end
|
32
35
|
end
|
@@ -20,6 +20,7 @@ class ActiveRecallGenerator < Rails::Generators::Base
|
|
20
20
|
def create_migration_files
|
21
21
|
create_migration_file_if_not_exist "create_active_recall_tables"
|
22
22
|
create_migration_file_if_not_exist "add_active_recall_item_answer_counts"
|
23
|
+
create_migration_file_if_not_exist "add_active_recall_item_easiness_factor"
|
23
24
|
create_migration_file_if_not_exist "migrate_okubo_to_active_recall" if options["migrate_data"]
|
24
25
|
end
|
25
26
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddActiveRecallItemEasinessFactor < ActiveRecord::Migration[5.2]
|
4
|
+
def self.up
|
5
|
+
add_column :active_recall_items, :easiness_factor, :float, default: 2.5
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down
|
9
|
+
remove_column :active_recall_items, :easiness_factor
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_recall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Gravina
|
@@ -9,8 +9,42 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: appraisal
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rails
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '6.0'
|
35
|
+
- - "<="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '7.2'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '6.0'
|
45
|
+
- - "<="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '7.2'
|
14
48
|
- !ruby/object:Gem::Dependency
|
15
49
|
name: rake
|
16
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,40 +107,40 @@ dependencies:
|
|
73
107
|
requirements:
|
74
108
|
- - ">="
|
75
109
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
110
|
+
version: '6.0'
|
77
111
|
- - "<="
|
78
112
|
- !ruby/object:Gem::Version
|
79
|
-
version: '7.
|
113
|
+
version: '7.2'
|
80
114
|
type: :runtime
|
81
115
|
prerelease: false
|
82
116
|
version_requirements: !ruby/object:Gem::Requirement
|
83
117
|
requirements:
|
84
118
|
- - ">="
|
85
119
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
120
|
+
version: '6.0'
|
87
121
|
- - "<="
|
88
122
|
- !ruby/object:Gem::Version
|
89
|
-
version: '7.
|
123
|
+
version: '7.2'
|
90
124
|
- !ruby/object:Gem::Dependency
|
91
125
|
name: activesupport
|
92
126
|
requirement: !ruby/object:Gem::Requirement
|
93
127
|
requirements:
|
94
128
|
- - ">="
|
95
129
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
130
|
+
version: '6.0'
|
97
131
|
- - "<="
|
98
132
|
- !ruby/object:Gem::Version
|
99
|
-
version: '7.
|
133
|
+
version: '7.2'
|
100
134
|
type: :runtime
|
101
135
|
prerelease: false
|
102
136
|
version_requirements: !ruby/object:Gem::Requirement
|
103
137
|
requirements:
|
104
138
|
- - ">="
|
105
139
|
- !ruby/object:Gem::Version
|
106
|
-
version:
|
140
|
+
version: '6.0'
|
107
141
|
- - "<="
|
108
142
|
- !ruby/object:Gem::Version
|
109
|
-
version: '7.
|
143
|
+
version: '7.2'
|
110
144
|
description: A spaced-repetition system to be used with ActiveRecord models
|
111
145
|
email:
|
112
146
|
- robert.gravina@gmail.com
|
@@ -119,6 +153,7 @@ files:
|
|
119
153
|
- ".gitignore"
|
120
154
|
- ".rspec"
|
121
155
|
- ".tool-versions"
|
156
|
+
- Appraisals
|
122
157
|
- Gemfile
|
123
158
|
- Gemfile.lock
|
124
159
|
- LICENSE
|
@@ -127,9 +162,19 @@ files:
|
|
127
162
|
- active_recall.gemspec
|
128
163
|
- bin/console
|
129
164
|
- bin/setup
|
165
|
+
- bin/spec
|
166
|
+
- gemfiles/rails_6_0.gemfile
|
167
|
+
- gemfiles/rails_6_0.gemfile.lock
|
168
|
+
- gemfiles/rails_6_1.gemfile
|
169
|
+
- gemfiles/rails_6_1.gemfile.lock
|
170
|
+
- gemfiles/rails_7_0.gemfile
|
171
|
+
- gemfiles/rails_7_0.gemfile.lock
|
172
|
+
- gemfiles/rails_7_1.gemfile
|
173
|
+
- gemfiles/rails_7_1.gemfile.lock
|
130
174
|
- lib/active_recall.rb
|
131
175
|
- lib/active_recall/algorithms/fibonacci_sequence.rb
|
132
176
|
- lib/active_recall/algorithms/leitner_system.rb
|
177
|
+
- lib/active_recall/algorithms/sm2.rb
|
133
178
|
- lib/active_recall/algorithms/soft_leitner_system.rb
|
134
179
|
- lib/active_recall/base.rb
|
135
180
|
- lib/active_recall/configuration.rb
|
@@ -140,6 +185,7 @@ files:
|
|
140
185
|
- lib/active_recall/version.rb
|
141
186
|
- lib/generators/active_recall/active_recall_generator.rb
|
142
187
|
- lib/generators/active_recall/templates/add_active_recall_item_answer_counts.rb
|
188
|
+
- lib/generators/active_recall/templates/add_active_recall_item_easiness_factor.rb
|
143
189
|
- lib/generators/active_recall/templates/create_active_recall_tables.rb
|
144
190
|
- lib/generators/active_recall/templates/migrate_okubo_to_active_recall.rb
|
145
191
|
homepage: https://github.com/jaysonvirissimo/active_recall
|
@@ -155,14 +201,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
201
|
requirements:
|
156
202
|
- - ">="
|
157
203
|
- !ruby/object:Gem::Version
|
158
|
-
version: '
|
204
|
+
version: '3.0'
|
159
205
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
206
|
requirements:
|
161
207
|
- - ">="
|
162
208
|
- !ruby/object:Gem::Version
|
163
209
|
version: '0'
|
164
210
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
211
|
+
rubygems_version: 3.5.3
|
166
212
|
signing_key:
|
167
213
|
specification_version: 4
|
168
214
|
summary: A spaced-repetition system
|