active_recall 2.2.0 → 2.3.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/Gemfile.lock +13 -2
- data/README.md +115 -27
- data/active_recall.gemspec +4 -0
- data/gemfiles/rails_7_0.gemfile.lock +15 -4
- data/gemfiles/rails_7_1.gemfile.lock +15 -4
- data/gemfiles/rails_8_0.gemfile.lock +20 -4
- data/lib/active_recall/algorithms/fsrs.rb +110 -0
- data/lib/active_recall/configuration.rb +4 -1
- data/lib/active_recall/version.rb +1 -1
- data/lib/active_recall.rb +1 -0
- data/lib/generators/active_recall/active_recall_generator.rb +1 -0
- data/lib/generators/active_recall/templates/add_active_recall_item_fsrs_fields.rb +21 -0
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25a05ad2ad88bc270c9e58cb8a2760b280677fb6d1d3c35ee76df8b021e1c6fd
|
|
4
|
+
data.tar.gz: 1503c4d5cc0863a42d51a084c7651d79fe574f9d98fb60254fd2084b6cf4ec13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aab226bca430aac827eb11c7c46b1765f6b367181e233a614748f509640589306d86cd3fc4ad32655b431f4b49d5beaa163f056b7dfb09ccd51a17e5d727cc9b
|
|
7
|
+
data.tar.gz: c630d5a2fe83551b8929ef1a9f22b9408c908f3cb90a376b56dd056cdfa90e0c530f0c6aac02cae4d3a6d6891b457db6022258cf549687241783954a61ceb65f
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.1)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
|
+
fsrs (>= 0.9.2, < 1.0)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
@@ -98,6 +99,8 @@ GEM
|
|
|
98
99
|
drb (2.2.0)
|
|
99
100
|
ruby2_keywords
|
|
100
101
|
erubi (1.12.0)
|
|
102
|
+
fsrs (0.9.2)
|
|
103
|
+
activesupport (>= 7.0, < 9.0)
|
|
101
104
|
globalid (1.2.1)
|
|
102
105
|
activesupport (>= 6.1)
|
|
103
106
|
i18n (1.14.1)
|
|
@@ -119,7 +122,7 @@ GEM
|
|
|
119
122
|
net-smtp
|
|
120
123
|
marcel (1.0.2)
|
|
121
124
|
mini_mime (1.1.5)
|
|
122
|
-
mini_portile2 (2.8.
|
|
125
|
+
mini_portile2 (2.8.9)
|
|
123
126
|
minitest (5.18.1)
|
|
124
127
|
mutex_m (0.2.0)
|
|
125
128
|
net-imap (0.4.9.1)
|
|
@@ -135,6 +138,10 @@ GEM
|
|
|
135
138
|
nokogiri (1.18.7)
|
|
136
139
|
mini_portile2 (~> 2.8.2)
|
|
137
140
|
racc (~> 1.4)
|
|
141
|
+
nokogiri (1.18.7-arm64-darwin)
|
|
142
|
+
racc (~> 1.4)
|
|
143
|
+
nokogiri (1.18.7-x86_64-darwin)
|
|
144
|
+
racc (~> 1.4)
|
|
138
145
|
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
139
146
|
racc (~> 1.4)
|
|
140
147
|
parallel (1.24.0)
|
|
@@ -219,6 +226,8 @@ GEM
|
|
|
219
226
|
ruby2_keywords (0.0.5)
|
|
220
227
|
sqlite3 (2.5.0)
|
|
221
228
|
mini_portile2 (~> 2.8.0)
|
|
229
|
+
sqlite3 (2.5.0-arm64-darwin)
|
|
230
|
+
sqlite3 (2.5.0-x86_64-darwin)
|
|
222
231
|
sqlite3 (2.5.0-x86_64-linux-gnu)
|
|
223
232
|
standard (1.35.1)
|
|
224
233
|
language_server-protocol (~> 3.17.0.2)
|
|
@@ -244,7 +253,9 @@ GEM
|
|
|
244
253
|
zeitwerk (2.6.12)
|
|
245
254
|
|
|
246
255
|
PLATFORMS
|
|
256
|
+
arm64-darwin
|
|
247
257
|
ruby
|
|
258
|
+
x86_64-darwin
|
|
248
259
|
x86_64-linux
|
|
249
260
|
|
|
250
261
|
DEPENDENCIES
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ActiveRecall
|
|
2
2
|
|
|
3
3
|
**ActiveRecall** is a spaced-repetition system that allows you to treat arbitrary [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord) models as if they were flashcards to be learned and reviewed.
|
|
4
|
-
It
|
|
4
|
+
It is based on, and is intended to be backwards compatible with, the [okubo](https://github.com/rgravina/okubo) gem.
|
|
5
5
|
The primary differentiating features are that it lets the user specify the scheduling algorithm and is fully compatible with (and requires) Rails 6+ and Ruby 3+.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
@@ -28,17 +28,13 @@ Or install it yourself as:
|
|
|
28
28
|
|
|
29
29
|
$ gem install active_recall
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
You can configure the desired SRS algorithm during runtime:
|
|
33
|
-
```ruby
|
|
34
|
-
ActiveRecall.configure do |config|
|
|
35
|
-
config.algorithm_class = ActiveRecall::FibonacciSequence
|
|
36
|
-
end
|
|
37
|
-
```
|
|
38
|
-
Algorithms include `FibonacciSequence`, `LeitnerSystem`, `SoftLeitnerSystem`, and `SM2` (see [here](https://en.wikipedia.org/wiki/SuperMemo#Description_of_SM-2_algorithm)).
|
|
39
|
-
For Rails applications, try doing this from within an [initializer file](https://guides.rubyonrails.org/configuring.html#using-initializer-files).
|
|
31
|
+
The generator creates all the migrations any algorithm needs (including the `easiness_factor` column for SM2 and the FSRS-specific columns), so you don't have to revisit migrations when you switch algorithms later.
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
The fastest way to get going — no algorithm choice, no grade scale, just right/wrong feedback. This uses the default `LeitnerSystem`.
|
|
36
|
+
|
|
37
|
+
Suppose you have an application allowing your users to study words in a foreign language. Use `has_deck` to set up a deck of flashcards:
|
|
42
38
|
|
|
43
39
|
```ruby
|
|
44
40
|
class Word < ActiveRecord::Base
|
|
@@ -48,18 +44,76 @@ class User < ActiveRecord::Base
|
|
|
48
44
|
has_deck :words
|
|
49
45
|
end
|
|
50
46
|
|
|
51
|
-
user = User.create!(:
|
|
52
|
-
word = Word.create!(:
|
|
47
|
+
user = User.create!(name: "Robert")
|
|
48
|
+
word = Word.create!(kanji: "日本語", kana: "にほんご", translation: "Japanese language")
|
|
49
|
+
|
|
50
|
+
user.words << word
|
|
51
|
+
user.words.untested #=> [word]
|
|
52
|
+
|
|
53
|
+
user.right_answer_for!(word)
|
|
54
|
+
user.words.known #=> [word]
|
|
55
|
+
|
|
56
|
+
user.wrong_answer_for!(word)
|
|
57
|
+
user.words.failed #=> [word]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That's it. Want graded feedback (Again/Hard/Good/Easy) or modern scheduling? See [Choosing an Algorithm](#choosing-an-algorithm) below.
|
|
61
|
+
|
|
62
|
+
## Choosing an Algorithm
|
|
63
|
+
|
|
64
|
+
> **Not sure which to pick?** Stick with the default `LeitnerSystem` — it works out of the box and only needs right/wrong feedback. Reach for `FSRS` when you want modern, evidence-based scheduling and are willing to collect 1–4 ratings ("Again / Hard / Good / Easy") from users.
|
|
65
|
+
|
|
66
|
+
The full menu, in increasing order of sophistication:
|
|
67
|
+
|
|
68
|
+
| Algorithm | Type | How you grade | Reach for it when |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| **`LeitnerSystem`** *(default — start here)* | binary | `right_answer_for!` / `wrong_answer_for!` | You want the simplest thing that works |
|
|
71
|
+
| `SoftLeitnerSystem` | binary | `right_answer_for!` / `wrong_answer_for!` | Leitner is too punishing on occasional lapses |
|
|
72
|
+
| `FibonacciSequence` | binary | `right_answer_for!` / `wrong_answer_for!` | You want faster-growing intervals than Leitner |
|
|
73
|
+
| `SM2` | gradable | `score!(0..5, item)` | You want the classic SuperMemo behavior users know from Anki |
|
|
74
|
+
| **`FSRS`** *(modern recommendation)* | gradable | `score!(1..4, item)` | You're building something serious and want best-in-class retention |
|
|
75
|
+
|
|
76
|
+
**Binary** algorithms expect right-or-wrong feedback (`user.right_answer_for!(item)` / `user.wrong_answer_for!(item)`). **Gradable** algorithms expect a numeric grade per review (`user.score!(grade, item)`). Mixing them — e.g. calling `right_answer_for!` while configured to use SM2 — raises `ActiveRecall::IncompatibleAlgorithmError`.
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
Skip this section if you're sticking with the default `LeitnerSystem` — there's nothing to configure.
|
|
81
|
+
|
|
82
|
+
To switch algorithms, set `algorithm_class` from a Rails [initializer file](https://guides.rubyonrails.org/configuring.html#using-initializer-files):
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# config/initializers/active_recall.rb
|
|
86
|
+
ActiveRecall.configure do |config|
|
|
87
|
+
config.algorithm_class = ActiveRecall::FSRS # or SM2, SoftLeitnerSystem, FibonacciSequence
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### FSRS-specific configuration
|
|
92
|
+
|
|
93
|
+
FSRS exposes three optional knobs. All have sensible defaults; tune only if you have a reason to:
|
|
94
|
+
|
|
95
|
+
- `fsrs_request_retention` — target retention probability (default `0.9`). Lower → longer intervals, more forgetting tolerated.
|
|
96
|
+
- `fsrs_maximum_interval` — caps the scheduled interval, in days.
|
|
97
|
+
- `fsrs_weights` — array of FSRS weights for advanced tuning.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
ActiveRecall.configure do |config|
|
|
101
|
+
config.algorithm_class = ActiveRecall::FSRS
|
|
102
|
+
config.fsrs_request_retention = 0.85
|
|
103
|
+
config.fsrs_maximum_interval = 365
|
|
104
|
+
end
|
|
53
105
|
```
|
|
54
106
|
|
|
55
|
-
|
|
107
|
+
## Usage with binary algorithms
|
|
108
|
+
|
|
109
|
+
Applies to `LeitnerSystem`, `SoftLeitnerSystem`, and `FibonacciSequence`.
|
|
56
110
|
|
|
57
111
|
```ruby
|
|
58
112
|
# Initially adding a word
|
|
59
113
|
user.words << word
|
|
60
114
|
user.words.untested #=> [word]
|
|
61
115
|
|
|
62
|
-
# Guessing a word correctly
|
|
116
|
+
# Guessing a word correctly
|
|
63
117
|
user.right_answer_for!(word)
|
|
64
118
|
user.words.known #=> [word]
|
|
65
119
|
|
|
@@ -75,11 +129,11 @@ As time passes, words need to be reviewed to keep them fresh in memory:
|
|
|
75
129
|
|
|
76
130
|
```ruby
|
|
77
131
|
# Three days later...
|
|
78
|
-
user.words.known
|
|
132
|
+
user.words.known #=> []
|
|
79
133
|
user.words.expired #=> [word]
|
|
80
134
|
```
|
|
81
135
|
|
|
82
|
-
Guessing a word correctly several times in a row
|
|
136
|
+
Guessing a word correctly several times in a row makes the word take longer to expire, demonstrating mastery:
|
|
83
137
|
|
|
84
138
|
```ruby
|
|
85
139
|
user.right_answer_for!(word)
|
|
@@ -93,21 +147,55 @@ user.right_answer_for!(word)
|
|
|
93
147
|
user.words.expired #=> [word]
|
|
94
148
|
```
|
|
95
149
|
|
|
96
|
-
|
|
150
|
+
## Usage with SM2
|
|
151
|
+
|
|
152
|
+
[SM2](https://en.wikipedia.org/wiki/SuperMemo#Description_of_SM-2_algorithm) uses a 0–5 grade scale:
|
|
153
|
+
|
|
154
|
+
| Grade | Meaning |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `5` | Perfect response |
|
|
157
|
+
| `4` | Correct response after a hesitation |
|
|
158
|
+
| `3` | Correct response recalled with serious difficulty |
|
|
159
|
+
| `2` | Incorrect response, but close |
|
|
160
|
+
| `1` | Incorrect response with familiarity |
|
|
161
|
+
| `0` | Complete blackout |
|
|
162
|
+
|
|
163
|
+
Grades **≥ 3** count as a success: the box advances and `times_right` increments. Grades **< 3** reset the box to `0` and increment `times_wrong`. Each item's `easiness_factor` starts at `2.5` and is clamped to a minimum of `1.3`.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
user.words << word
|
|
167
|
+
|
|
168
|
+
user.score!(5, word) # perfect recall — box advances, EF rises
|
|
169
|
+
user.score!(2, word) # incorrect — box resets to 0
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Calling `user.right_answer_for!(word)` while SM2 is configured raises `ActiveRecall::IncompatibleAlgorithmError` — use `score!` instead.
|
|
173
|
+
|
|
174
|
+
## Usage with FSRS
|
|
175
|
+
|
|
176
|
+
[FSRS](https://github.com/open-spaced-repetition/fsrs4anki) uses a 1–4 grade scale matching the familiar Anki buttons:
|
|
177
|
+
|
|
178
|
+
| Grade | Meaning |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `1` | Again (lapse) |
|
|
181
|
+
| `2` | Hard |
|
|
182
|
+
| `3` | Good |
|
|
183
|
+
| `4` | Easy |
|
|
184
|
+
|
|
185
|
+
FSRS tracks `stability`, `difficulty`, `state`, and `lapses` per item. Those columns are added automatically by `rails generate active_recall` — no extra setup needed.
|
|
186
|
+
|
|
97
187
|
```ruby
|
|
98
|
-
|
|
99
|
-
user.score!(grade, word)
|
|
188
|
+
user.words << word
|
|
100
189
|
|
|
101
|
-
#
|
|
102
|
-
user.
|
|
103
|
-
=> ActiveRecall::IncompatibleAlgorithmError
|
|
190
|
+
user.score!(3, word) # "Good" — typical successful recall
|
|
191
|
+
user.score!(1, word) # "Again" — counts as a lapse
|
|
104
192
|
```
|
|
105
193
|
|
|
106
|
-
|
|
107
|
-
|
|
194
|
+
Calling `user.right_answer_for!(word)` while FSRS is configured raises `ActiveRecall::IncompatibleAlgorithmError` — use `score!` instead.
|
|
195
|
+
|
|
196
|
+
## Reviewing
|
|
108
197
|
|
|
109
|
-
In addition to
|
|
110
|
-
Words are randomly chosen from all untested words, failed, and finally expired in order of precedence.
|
|
198
|
+
In addition to the `expired` scope, ActiveRecall provides a suggested reviewing sequence for all unknown words in the deck. Words are randomly chosen from `untested`, `failed`, and `expired` items, in that order of precedence. This works the same for every algorithm.
|
|
111
199
|
|
|
112
200
|
```ruby
|
|
113
201
|
user.words.review #=> [word]
|
data/active_recall.gemspec
CHANGED
|
@@ -41,5 +41,9 @@ Gem::Specification.new do |spec|
|
|
|
41
41
|
spec.add_development_dependency "standard"
|
|
42
42
|
spec.add_runtime_dependency "activerecord", ">= 7.0", "< 9.0"
|
|
43
43
|
spec.add_runtime_dependency "activesupport", ">= 7.0", "< 9.0"
|
|
44
|
+
# fsrs 0.9.2 is the first release that widens activesupport to allow Rails 8
|
|
45
|
+
# and ships the new-card scheduling fix (minute-vs-day in schedule_new_state).
|
|
46
|
+
# Do not relax this floor without verifying both still hold.
|
|
47
|
+
spec.add_runtime_dependency "fsrs", ">= 0.9.2", "< 1.0"
|
|
44
48
|
spec.required_ruby_version = ">= 3.2"
|
|
45
49
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.1)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
|
+
fsrs (>= 0.9.2, < 1.0)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
@@ -98,6 +99,8 @@ GEM
|
|
|
98
99
|
drb (2.2.0)
|
|
99
100
|
ruby2_keywords
|
|
100
101
|
erubi (1.12.0)
|
|
102
|
+
fsrs (0.9.2)
|
|
103
|
+
activesupport (>= 7.0, < 9.0)
|
|
101
104
|
globalid (1.2.1)
|
|
102
105
|
activesupport (>= 6.1)
|
|
103
106
|
i18n (1.14.1)
|
|
@@ -132,8 +135,15 @@ GEM
|
|
|
132
135
|
net-smtp (0.4.0.1)
|
|
133
136
|
net-protocol
|
|
134
137
|
nio4r (2.7.0)
|
|
138
|
+
nokogiri (1.18.7)
|
|
139
|
+
mini_portile2 (~> 2.8.2)
|
|
140
|
+
racc (~> 1.4)
|
|
135
141
|
nokogiri (1.18.7-arm64-darwin)
|
|
136
142
|
racc (~> 1.4)
|
|
143
|
+
nokogiri (1.18.7-x86_64-darwin)
|
|
144
|
+
racc (~> 1.4)
|
|
145
|
+
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
146
|
+
racc (~> 1.4)
|
|
137
147
|
parallel (1.24.0)
|
|
138
148
|
parser (3.3.1.0)
|
|
139
149
|
ast (~> 2.4.1)
|
|
@@ -244,9 +254,10 @@ GEM
|
|
|
244
254
|
zeitwerk (2.6.12)
|
|
245
255
|
|
|
246
256
|
PLATFORMS
|
|
247
|
-
arm64-darwin
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
arm64-darwin
|
|
258
|
+
ruby
|
|
259
|
+
x86_64-darwin
|
|
260
|
+
x86_64-linux
|
|
250
261
|
|
|
251
262
|
DEPENDENCIES
|
|
252
263
|
active_recall!
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.1)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
|
+
fsrs (>= 0.9.2, < 1.0)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
@@ -98,6 +99,8 @@ GEM
|
|
|
98
99
|
drb (2.2.0)
|
|
99
100
|
ruby2_keywords
|
|
100
101
|
erubi (1.12.0)
|
|
102
|
+
fsrs (0.9.2)
|
|
103
|
+
activesupport (>= 7.0, < 9.0)
|
|
101
104
|
globalid (1.2.1)
|
|
102
105
|
activesupport (>= 6.1)
|
|
103
106
|
i18n (1.14.1)
|
|
@@ -132,8 +135,15 @@ GEM
|
|
|
132
135
|
net-smtp (0.4.0.1)
|
|
133
136
|
net-protocol
|
|
134
137
|
nio4r (2.7.0)
|
|
138
|
+
nokogiri (1.18.7)
|
|
139
|
+
mini_portile2 (~> 2.8.2)
|
|
140
|
+
racc (~> 1.4)
|
|
135
141
|
nokogiri (1.18.7-arm64-darwin)
|
|
136
142
|
racc (~> 1.4)
|
|
143
|
+
nokogiri (1.18.7-x86_64-darwin)
|
|
144
|
+
racc (~> 1.4)
|
|
145
|
+
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
146
|
+
racc (~> 1.4)
|
|
137
147
|
parallel (1.24.0)
|
|
138
148
|
parser (3.3.1.0)
|
|
139
149
|
ast (~> 2.4.1)
|
|
@@ -244,9 +254,10 @@ GEM
|
|
|
244
254
|
zeitwerk (2.6.12)
|
|
245
255
|
|
|
246
256
|
PLATFORMS
|
|
247
|
-
arm64-darwin
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
arm64-darwin
|
|
258
|
+
ruby
|
|
259
|
+
x86_64-darwin
|
|
260
|
+
x86_64-linux
|
|
250
261
|
|
|
251
262
|
DEPENDENCIES
|
|
252
263
|
active_recall!
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.1)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
|
+
fsrs (>= 0.9.2, < 1.0)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
@@ -95,6 +96,8 @@ GEM
|
|
|
95
96
|
diff-lcs (1.5.1)
|
|
96
97
|
drb (2.2.1)
|
|
97
98
|
erubi (1.13.1)
|
|
99
|
+
fsrs (0.9.2)
|
|
100
|
+
activesupport (>= 7.0, < 9.0)
|
|
98
101
|
globalid (1.2.1)
|
|
99
102
|
activesupport (>= 6.1)
|
|
100
103
|
i18n (1.14.6)
|
|
@@ -117,6 +120,7 @@ GEM
|
|
|
117
120
|
net-smtp
|
|
118
121
|
marcel (1.0.4)
|
|
119
122
|
mini_mime (1.1.5)
|
|
123
|
+
mini_portile2 (2.8.9)
|
|
120
124
|
minitest (5.25.4)
|
|
121
125
|
net-imap (0.5.4)
|
|
122
126
|
date
|
|
@@ -128,8 +132,15 @@ GEM
|
|
|
128
132
|
net-smtp (0.5.0)
|
|
129
133
|
net-protocol
|
|
130
134
|
nio4r (2.7.4)
|
|
135
|
+
nokogiri (1.18.0)
|
|
136
|
+
mini_portile2 (~> 2.8.2)
|
|
137
|
+
racc (~> 1.4)
|
|
131
138
|
nokogiri (1.18.0-arm64-darwin)
|
|
132
139
|
racc (~> 1.4)
|
|
140
|
+
nokogiri (1.18.0-x86_64-darwin)
|
|
141
|
+
racc (~> 1.4)
|
|
142
|
+
nokogiri (1.18.0-x86_64-linux-gnu)
|
|
143
|
+
racc (~> 1.4)
|
|
133
144
|
parallel (1.26.3)
|
|
134
145
|
parser (3.3.6.0)
|
|
135
146
|
ast (~> 2.4.1)
|
|
@@ -211,7 +222,11 @@ GEM
|
|
|
211
222
|
rubocop-ast (>= 1.31.1, < 2.0)
|
|
212
223
|
ruby-progressbar (1.13.0)
|
|
213
224
|
securerandom (0.4.1)
|
|
225
|
+
sqlite3 (2.5.0)
|
|
226
|
+
mini_portile2 (~> 2.8.0)
|
|
214
227
|
sqlite3 (2.5.0-arm64-darwin)
|
|
228
|
+
sqlite3 (2.5.0-x86_64-darwin)
|
|
229
|
+
sqlite3 (2.5.0-x86_64-linux-gnu)
|
|
215
230
|
standard (1.43.0)
|
|
216
231
|
language_server-protocol (~> 3.17.0.2)
|
|
217
232
|
lint_roller (~> 1.0)
|
|
@@ -240,9 +255,10 @@ GEM
|
|
|
240
255
|
zeitwerk (2.7.1)
|
|
241
256
|
|
|
242
257
|
PLATFORMS
|
|
243
|
-
arm64-darwin
|
|
244
|
-
|
|
245
|
-
|
|
258
|
+
arm64-darwin
|
|
259
|
+
ruby
|
|
260
|
+
x86_64-darwin
|
|
261
|
+
x86_64-linux
|
|
246
262
|
|
|
247
263
|
DEPENDENCIES
|
|
248
264
|
active_recall!
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fsrs"
|
|
4
|
+
|
|
5
|
+
module ActiveRecall
|
|
6
|
+
class FSRS
|
|
7
|
+
REQUIRED_ATTRIBUTES = [
|
|
8
|
+
:box,
|
|
9
|
+
:stability,
|
|
10
|
+
:difficulty,
|
|
11
|
+
:state,
|
|
12
|
+
:lapses,
|
|
13
|
+
:elapsed_days,
|
|
14
|
+
:scheduled_days,
|
|
15
|
+
:times_right,
|
|
16
|
+
:times_wrong,
|
|
17
|
+
:last_reviewed,
|
|
18
|
+
:grade
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
GRADE_TO_RATING = {
|
|
22
|
+
1 => Fsrs::Rating::AGAIN,
|
|
23
|
+
2 => Fsrs::Rating::HARD,
|
|
24
|
+
3 => Fsrs::Rating::GOOD,
|
|
25
|
+
4 => Fsrs::Rating::EASY
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def self.required_attributes
|
|
29
|
+
REQUIRED_ATTRIBUTES
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.type
|
|
33
|
+
:gradable
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.score(**)
|
|
37
|
+
new(**).score
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(box:, stability:, difficulty:, state:, lapses:,
|
|
41
|
+
elapsed_days:, scheduled_days:, times_right:, times_wrong:,
|
|
42
|
+
last_reviewed:, grade:, current_time: Time.current)
|
|
43
|
+
@box = box || 0
|
|
44
|
+
@stability = stability
|
|
45
|
+
@difficulty = difficulty
|
|
46
|
+
@state = state || Fsrs::State::NEW
|
|
47
|
+
@lapses = lapses || 0
|
|
48
|
+
@elapsed_days = elapsed_days || 0
|
|
49
|
+
@scheduled_days = scheduled_days || 0
|
|
50
|
+
@times_right = times_right || 0
|
|
51
|
+
@times_wrong = times_wrong || 0
|
|
52
|
+
@last_reviewed = last_reviewed
|
|
53
|
+
@grade = grade
|
|
54
|
+
@current_time = current_time
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def score
|
|
58
|
+
raise "Grade must be between 1-4!" unless GRADE_TO_RATING.key?(@grade)
|
|
59
|
+
|
|
60
|
+
now = to_utc_datetime(@current_time)
|
|
61
|
+
scheduling = scheduler.repeat(build_card, now)
|
|
62
|
+
result = scheduling[GRADE_TO_RATING[@grade]].card
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
box: result.reps,
|
|
66
|
+
stability: result.stability,
|
|
67
|
+
difficulty: result.difficulty,
|
|
68
|
+
state: result.state,
|
|
69
|
+
lapses: result.lapses,
|
|
70
|
+
elapsed_days: result.elapsed_days,
|
|
71
|
+
scheduled_days: result.scheduled_days,
|
|
72
|
+
last_reviewed: result.last_review,
|
|
73
|
+
next_review: result.due,
|
|
74
|
+
times_right: @times_right + ((@grade >= 2) ? 1 : 0),
|
|
75
|
+
times_wrong: @times_wrong + ((@grade == 1) ? 1 : 0)
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def build_card
|
|
82
|
+
card = Fsrs::Card.new
|
|
83
|
+
card.stability = @stability if @stability
|
|
84
|
+
card.difficulty = @difficulty if @difficulty
|
|
85
|
+
card.state = @state
|
|
86
|
+
card.lapses = @lapses
|
|
87
|
+
card.elapsed_days = @elapsed_days
|
|
88
|
+
card.scheduled_days = @scheduled_days
|
|
89
|
+
card.reps = @box
|
|
90
|
+
card.last_review = to_utc_datetime(@last_reviewed) if @last_reviewed
|
|
91
|
+
card
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def scheduler
|
|
95
|
+
scheduler = Fsrs::Scheduler.new
|
|
96
|
+
config = ActiveRecall.configuration
|
|
97
|
+
scheduler.p.request_retention = config.fsrs_request_retention if config.fsrs_request_retention
|
|
98
|
+
scheduler.p.maximum_interval = config.fsrs_maximum_interval if config.fsrs_maximum_interval
|
|
99
|
+
scheduler.p.w = config.fsrs_weights if config.fsrs_weights
|
|
100
|
+
scheduler
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_utc_datetime(value)
|
|
104
|
+
case value
|
|
105
|
+
when DateTime then value.new_offset(0)
|
|
106
|
+
else value.utc.to_datetime
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/active_recall.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "active_recall/algorithms/fibonacci_sequence"
|
|
|
7
7
|
require "active_recall/algorithms/leitner_system"
|
|
8
8
|
require "active_recall/algorithms/soft_leitner_system"
|
|
9
9
|
require "active_recall/algorithms/sm2"
|
|
10
|
+
require "active_recall/algorithms/fsrs"
|
|
10
11
|
require "active_recall/configuration"
|
|
11
12
|
require "active_recall/models/deck"
|
|
12
13
|
require "active_recall/models/item"
|
|
@@ -21,6 +21,7 @@ class ActiveRecallGenerator < Rails::Generators::Base
|
|
|
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
23
|
create_migration_file_if_not_exist "add_active_recall_item_easiness_factor"
|
|
24
|
+
create_migration_file_if_not_exist "add_active_recall_item_fsrs_fields"
|
|
24
25
|
create_migration_file_if_not_exist "migrate_okubo_to_active_recall" if options["migrate_data"]
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddActiveRecallItemFsrsFields < ActiveRecord::Migration[5.2]
|
|
4
|
+
def self.up
|
|
5
|
+
add_column :active_recall_items, :stability, :float
|
|
6
|
+
add_column :active_recall_items, :difficulty, :float
|
|
7
|
+
add_column :active_recall_items, :state, :integer, default: 0
|
|
8
|
+
add_column :active_recall_items, :lapses, :integer, default: 0
|
|
9
|
+
add_column :active_recall_items, :elapsed_days, :integer, default: 0
|
|
10
|
+
add_column :active_recall_items, :scheduled_days, :integer, default: 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.down
|
|
14
|
+
remove_column :active_recall_items, :stability
|
|
15
|
+
remove_column :active_recall_items, :difficulty
|
|
16
|
+
remove_column :active_recall_items, :state
|
|
17
|
+
remove_column :active_recall_items, :lapses
|
|
18
|
+
remove_column :active_recall_items, :elapsed_days
|
|
19
|
+
remove_column :active_recall_items, :scheduled_days
|
|
20
|
+
end
|
|
21
|
+
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: 2.
|
|
4
|
+
version: 2.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Gravina
|
|
@@ -154,6 +154,26 @@ dependencies:
|
|
|
154
154
|
- - "<"
|
|
155
155
|
- !ruby/object:Gem::Version
|
|
156
156
|
version: '9.0'
|
|
157
|
+
- !ruby/object:Gem::Dependency
|
|
158
|
+
name: fsrs
|
|
159
|
+
requirement: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: 0.9.2
|
|
164
|
+
- - "<"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '1.0'
|
|
167
|
+
type: :runtime
|
|
168
|
+
prerelease: false
|
|
169
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: 0.9.2
|
|
174
|
+
- - "<"
|
|
175
|
+
- !ruby/object:Gem::Version
|
|
176
|
+
version: '1.0'
|
|
157
177
|
description: A spaced-repetition system to be used with ActiveRecord models
|
|
158
178
|
email:
|
|
159
179
|
- robert.gravina@gmail.com
|
|
@@ -187,6 +207,7 @@ files:
|
|
|
187
207
|
- gemfiles/rails_8_0.gemfile.lock
|
|
188
208
|
- lib/active_recall.rb
|
|
189
209
|
- lib/active_recall/algorithms/fibonacci_sequence.rb
|
|
210
|
+
- lib/active_recall/algorithms/fsrs.rb
|
|
190
211
|
- lib/active_recall/algorithms/leitner_system.rb
|
|
191
212
|
- lib/active_recall/algorithms/sm2.rb
|
|
192
213
|
- lib/active_recall/algorithms/soft_leitner_system.rb
|
|
@@ -200,6 +221,7 @@ files:
|
|
|
200
221
|
- lib/generators/active_recall/active_recall_generator.rb
|
|
201
222
|
- lib/generators/active_recall/templates/add_active_recall_item_answer_counts.rb
|
|
202
223
|
- lib/generators/active_recall/templates/add_active_recall_item_easiness_factor.rb
|
|
224
|
+
- lib/generators/active_recall/templates/add_active_recall_item_fsrs_fields.rb
|
|
203
225
|
- lib/generators/active_recall/templates/create_active_recall_tables.rb
|
|
204
226
|
- lib/generators/active_recall/templates/migrate_okubo_to_active_recall.rb
|
|
205
227
|
- standard.yml
|