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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b09e91a09c75f943e1bb2ced582d4520674053f5504f8e14fcfe370babbf4300
4
- data.tar.gz: 9acaaad5775d5ff38694260e9d66983f97cd614ff5c52fde3bc67c7e13d14b01
3
+ metadata.gz: 25a05ad2ad88bc270c9e58cb8a2760b280677fb6d1d3c35ee76df8b021e1c6fd
4
+ data.tar.gz: 1503c4d5cc0863a42d51a084c7651d79fe574f9d98fb60254fd2084b6cf4ec13
5
5
  SHA512:
6
- metadata.gz: 6fffb380767376c2ad2b4bec163b508b2a32a03a33b05eebe755e80e6b0c825c1fd2a53133ae33e2582379e55329ef7a8b32dfbc3565a4fd4bca6ca2a93c994e
7
- data.tar.gz: 982a9a742fb97e0cc9f0db89d6664f253c7f4534929b030d3ce78d9c3d164b08e2087cc31da79fbda4fb69b2e58e2bd05362a436d37743bebab574a1c529a78a
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.2.0)
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.5)
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 it based on, and is intended to be backwards compatible with, the [okubo](https://github.com/rgravina/okubo) gem.
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
- ## Usage
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
- Assume you have an application allowing your users to study words in a foreign language. Using the `has_deck` method you can set up a deck of flashcards that the user will study:
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!(:name => "Robert")
52
- word = Word.create!(:kanji => "日本語", :kana => "にほんご", :translation => "Japanese language")
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
- You can add words and record attempts to guess the word as right or wrong. Various methods exist to allow you to access subsets of this collection:
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 (when using a binary algorithm)
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 results in the word taking longer to expire, and demonstrates mastery of that word.
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
- When using a gradable algorithm (rather than binary) such as the SM2 algorithm, you will need to supply your own grade along with the item:
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
- grade = 3
99
- user.score!(grade, word)
188
+ user.words << word
100
189
 
101
- # Using the binary-only methods will raise an error
102
- user.right_answer_for!(word)
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
- Reviewing
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 an `expired` method, ActiveRecall provides a suggested reviewing sequence for all unknown words in the deck.
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]
@@ -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.2.0)
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-23
248
- arm64-darwin-24
249
- arm64-darwin-25
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.2.0)
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-23
248
- arm64-darwin-24
249
- arm64-darwin-25
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.2.0)
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-23
244
- arm64-darwin-24
245
- arm64-darwin-25
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
@@ -2,7 +2,10 @@
2
2
 
3
3
  module ActiveRecall
4
4
  class Configuration
5
- attr_accessor :algorithm_class
5
+ attr_accessor :algorithm_class,
6
+ :fsrs_request_retention,
7
+ :fsrs_maximum_interval,
8
+ :fsrs_weights
6
9
 
7
10
  def initialize
8
11
  @algorithm_class = LeitnerSystem
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecall
4
- VERSION = "2.2.0"
4
+ VERSION = "2.3.1"
5
5
  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.2.0
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