active_recall 2.1.0 → 2.3.0
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 +5 -1
- data/.tool-versions +1 -1
- data/CLAUDE.md +194 -0
- data/Gemfile +14 -1
- data/Gemfile.lock +13 -4
- data/README.md +115 -27
- data/VENDORED_LICENSES.md +54 -0
- data/gemfiles/rails_7_0.gemfile +1 -0
- data/gemfiles/rails_7_0.gemfile.lock +17 -4
- data/gemfiles/rails_7_1.gemfile +1 -0
- data/gemfiles/rails_7_1.gemfile.lock +17 -4
- data/gemfiles/rails_8_0.gemfile +1 -0
- data/gemfiles/rails_8_0.gemfile.lock +17 -3
- data/lib/active_recall/algorithms/fibonacci_sequence.rb +4 -5
- data/lib/active_recall/algorithms/fsrs/internal.rb +335 -0
- data/lib/active_recall/algorithms/fsrs.rb +110 -0
- data/lib/active_recall/algorithms/sm2.rb +3 -1
- 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
- data/standard.yml +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 63a48f6351a76013bbf2922ca75b507b6dccb1ab23867896217b53bc6a77e26f
|
|
4
|
+
data.tar.gz: 9d8e3189bb8f5b1f15b284168e5973e87070bf223c0427b5a574cacd38c8e4b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efe496383029f35391f1b54533f525451c5ee8a02e440316b990b812923b2061fc94eeff2ed1d691b17a1838b2d266b51ec9dde7d3305d511b484edb53b606b7
|
|
7
|
+
data.tar.gz: ff9f67e94b77d93175af60926ad889655862989c18a9b2fb07b8e75503526b8281f32cd3a982ab1d70933363454e40461a10dd2be72784bc5097243173ff5b1e
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -21,6 +21,8 @@ jobs:
|
|
|
21
21
|
ruby:
|
|
22
22
|
- 3.2
|
|
23
23
|
- 3.3
|
|
24
|
+
- 3.4
|
|
25
|
+
- 4.0
|
|
24
26
|
allow_failures:
|
|
25
27
|
- false
|
|
26
28
|
env:
|
|
@@ -35,7 +37,9 @@ jobs:
|
|
|
35
37
|
uses: ruby/setup-ruby@v1
|
|
36
38
|
with:
|
|
37
39
|
ruby-version: ${{ matrix.ruby }}
|
|
38
|
-
bundler-cache:
|
|
40
|
+
bundler-cache: false
|
|
41
|
+
- name: Bundle Install
|
|
42
|
+
run: bundle install
|
|
39
43
|
- name: Install Appraisal Dependencies
|
|
40
44
|
run: bundle exec appraisal install
|
|
41
45
|
- name: Test with Rails 7.0
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 3.
|
|
1
|
+
ruby 3.4.8
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
ActiveRecall is a Ruby gem that implements a spaced-repetition system (SRS) for ActiveRecord models. It allows treating arbitrary ActiveRecord models as flashcards with configurable scheduling algorithms. The gem is backwards-compatible with the okubo gem and requires Rails 6+ and Ruby 3+.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
### Testing
|
|
12
|
+
```bash
|
|
13
|
+
# Run all tests (PREFERRED - handles appraisal automatically)
|
|
14
|
+
bin/spec
|
|
15
|
+
|
|
16
|
+
# Run tests against a specific Rails version
|
|
17
|
+
bin/spec rails-7-0
|
|
18
|
+
bin/spec rails-7-1
|
|
19
|
+
bin/spec rails-8-0
|
|
20
|
+
|
|
21
|
+
# Note: Direct bundle exec commands may encounter sqlite3 version conflicts
|
|
22
|
+
# The bin/spec wrapper handles this by using the appropriate appraisal gemfiles
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Linting
|
|
26
|
+
```bash
|
|
27
|
+
# Run StandardRB linter
|
|
28
|
+
bin/lint
|
|
29
|
+
# or
|
|
30
|
+
bundle exec standardrb
|
|
31
|
+
|
|
32
|
+
# Auto-fix issues
|
|
33
|
+
bundle exec standardrb --fix
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Development Setup
|
|
37
|
+
```bash
|
|
38
|
+
# Install dependencies and set up database
|
|
39
|
+
bin/setup
|
|
40
|
+
|
|
41
|
+
# Launch interactive console
|
|
42
|
+
bin/console
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Testing Against Multiple Rails Versions
|
|
46
|
+
```bash
|
|
47
|
+
# Generate gemfiles for different Rails versions (run after Gemfile changes)
|
|
48
|
+
bundle exec appraisal install
|
|
49
|
+
|
|
50
|
+
# Run tests against specific Rails version manually
|
|
51
|
+
bundle exec appraisal rails-7-0 rake spec
|
|
52
|
+
bundle exec appraisal rails-7-1 rake spec
|
|
53
|
+
bundle exec appraisal rails-8-0 rake spec
|
|
54
|
+
|
|
55
|
+
# Note: bin/spec runs tests against all Rails versions automatically
|
|
56
|
+
# This is defined in the Appraisals file and creates separate gemfiles in gemfiles/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Gem Management
|
|
60
|
+
```bash
|
|
61
|
+
# Build the gem
|
|
62
|
+
bundle exec rake build
|
|
63
|
+
|
|
64
|
+
# Install gem locally
|
|
65
|
+
bundle exec rake install
|
|
66
|
+
|
|
67
|
+
# Release new version (requires updating lib/active_recall/version.rb first)
|
|
68
|
+
bundle exec rake release
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Architecture
|
|
72
|
+
|
|
73
|
+
### Core Components
|
|
74
|
+
|
|
75
|
+
1. **ActiveRecall::Base** (lib/active_recall/base.rb)
|
|
76
|
+
- Provides `has_deck` class method that makes any ActiveRecord model a deck owner
|
|
77
|
+
- Mixes in DeckMethods and ItemMethods when invoked
|
|
78
|
+
- Sets up polymorphic relationship between users and their decks
|
|
79
|
+
|
|
80
|
+
2. **ActiveRecall::Deck** (lib/active_recall/models/deck.rb)
|
|
81
|
+
- Polymorphic model representing a collection of items to review
|
|
82
|
+
- Belongs to a user (polymorphic), has many items
|
|
83
|
+
- Key methods: `review`, `next`, `untested`, `failed`, `known`, `expired`, `box(n)`
|
|
84
|
+
- Handles database adapter differences (MySQL vs others) for random ordering
|
|
85
|
+
|
|
86
|
+
3. **ActiveRecall::Item** (lib/active_recall/models/item.rb)
|
|
87
|
+
- Represents individual flashcard items in a deck
|
|
88
|
+
- Polymorphic association to source objects (the actual flashcard content)
|
|
89
|
+
- Methods: `score!(grade)`, `right!`, `wrong!`
|
|
90
|
+
- Delegates to configured algorithm for calculating next review dates
|
|
91
|
+
|
|
92
|
+
4. **Algorithms** (lib/active_recall/algorithms/)
|
|
93
|
+
- Each algorithm class implements: `type`, `required_attributes`, `right`, `wrong`, and optionally `score`
|
|
94
|
+
- Algorithm types: `:binary` (right/wrong only) or `:gradable` (accepts scores)
|
|
95
|
+
- Binary algorithms: LeitnerSystem (default), SoftLeitnerSystem, FibonacciSequence
|
|
96
|
+
- Gradable algorithm: SM2
|
|
97
|
+
- All algorithms are stateless; they accept current state and return new state as a hash
|
|
98
|
+
|
|
99
|
+
5. **Configuration** (lib/active_recall/configuration.rb)
|
|
100
|
+
- Global configuration via `ActiveRecall.configure`
|
|
101
|
+
- Primary setting: `algorithm_class` (defaults to LeitnerSystem)
|
|
102
|
+
- Configuration should be set in Rails initializers
|
|
103
|
+
|
|
104
|
+
### Data Flow
|
|
105
|
+
|
|
106
|
+
1. A model calls `has_deck :items_name` to enable SRS functionality
|
|
107
|
+
2. This creates a Deck record (polymorphic, user_id/user_type)
|
|
108
|
+
3. Items are added to deck with `<<` operator, creating Item records
|
|
109
|
+
4. Item records store: box number, review dates, times_right/times_wrong, easiness_factor (for SM2)
|
|
110
|
+
5. User calls `right_answer_for!(item)` or `wrong_answer_for!(item)` (or `score!(grade, item)` for gradable algorithms)
|
|
111
|
+
6. This delegates to Item's `right!`/`wrong!`/`score!` which calls the configured algorithm
|
|
112
|
+
7. Algorithm returns hash of updated attributes (box, next_review, etc.)
|
|
113
|
+
8. Deck provides query methods that filter items by state and return source objects
|
|
114
|
+
|
|
115
|
+
### Key Design Patterns
|
|
116
|
+
|
|
117
|
+
- **Polymorphic associations**: Decks belong to any "user" model, Items reference any "source" model
|
|
118
|
+
- **Strategy pattern**: Algorithms are swappable via configuration
|
|
119
|
+
- **Delegation**: Deck methods delegate to Item scopes, which use the configured algorithm
|
|
120
|
+
- **Stateless algorithms**: All algorithm classes are stateless with class methods only (except FibonacciSequence which caches Fibonacci numbers in instances)
|
|
121
|
+
|
|
122
|
+
## Database Schema
|
|
123
|
+
|
|
124
|
+
The gem creates two tables via generators:
|
|
125
|
+
- `active_recall_decks`: id, user_id (polymorphic), user_type, created_at, updated_at
|
|
126
|
+
- `active_recall_items`: id, deck_id, source_id (polymorphic), source_type, box, times_right, times_wrong, last_reviewed, next_review, easiness_factor, created_at, updated_at
|
|
127
|
+
|
|
128
|
+
## Migration from Okubo
|
|
129
|
+
|
|
130
|
+
The gem includes a migration template (`migrate_okubo_to_active_recall.rb`) that renames tables from okubo_* to active_recall_*. Use `rails generate active_recall --migrate_data true` to generate this migration.
|
|
131
|
+
|
|
132
|
+
## Code Style
|
|
133
|
+
|
|
134
|
+
- Uses StandardRB for linting (config in standard.yml)
|
|
135
|
+
- All files have `# frozen_string_literal: true` pragma
|
|
136
|
+
- Ruby 3.2+ syntax required
|
|
137
|
+
|
|
138
|
+
## Testing Conventions
|
|
139
|
+
|
|
140
|
+
### Test File Structure
|
|
141
|
+
- Spec files mirror lib/ directory structure (e.g., `lib/active_recall/foo.rb` → `spec/active_recall/foo_spec.rb`)
|
|
142
|
+
- All spec files start with `# frozen_string_literal: true` and `require "spec_helper"`
|
|
143
|
+
- Use `describe ClassName` for top-level blocks
|
|
144
|
+
- Use `context` blocks to organize different scenarios
|
|
145
|
+
- Use `subject { described_class.new }` for the class under test
|
|
146
|
+
|
|
147
|
+
### Common Patterns
|
|
148
|
+
```ruby
|
|
149
|
+
# Basic structure
|
|
150
|
+
describe ActiveRecall::SomeClass do
|
|
151
|
+
subject { described_class.new }
|
|
152
|
+
|
|
153
|
+
describe "#method_name" do
|
|
154
|
+
it "describes expected behavior" do
|
|
155
|
+
expect(subject.method_name).to eq(expected_value)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context "when specific condition" do
|
|
160
|
+
it "behaves differently" do
|
|
161
|
+
# test implementation
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Shared Examples
|
|
168
|
+
- Algorithm specs use shared examples for common behavior (see spec/active_recall/algorithms/algorithm_spec.rb)
|
|
169
|
+
- Binary algorithms share: `it_behaves_like "binary spaced repetition algorithms"`
|
|
170
|
+
- This ensures consistent testing across algorithm implementations
|
|
171
|
+
|
|
172
|
+
### Test Data Setup
|
|
173
|
+
- Use `let` blocks for test data that needs to be created
|
|
174
|
+
- Use `before` blocks for setup that must run before each test
|
|
175
|
+
- The spec_helper.rb defines Word and User models for testing deck functionality
|
|
176
|
+
|
|
177
|
+
### Running Specific Tests
|
|
178
|
+
While `bin/spec` is preferred for full test runs, you can run individual specs during development:
|
|
179
|
+
```bash
|
|
180
|
+
# During active development, you may need to specify the appraisal
|
|
181
|
+
bundle exec appraisal rails-8-0 rspec spec/active_recall/configuration_spec.rb
|
|
182
|
+
|
|
183
|
+
# Or run all specs via bin/spec which handles all Rails versions
|
|
184
|
+
bin/spec
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Test Infrastructure Notes
|
|
188
|
+
- Tests use an in-memory SQLite database (`:memory:`) configured in spec/spec_helper.rb
|
|
189
|
+
- Different Rails versions require different sqlite3 gem versions:
|
|
190
|
+
- Rails 7.0/7.1 use sqlite3 ~> 1.4
|
|
191
|
+
- Rails 8.0 uses sqlite3 >= 2.1
|
|
192
|
+
- The appraisal system manages these version conflicts via separate gemfiles
|
|
193
|
+
- Always use `bin/spec` to avoid sqlite3 version conflict errors
|
|
194
|
+
- Direct `bundle exec rspec` commands will fail with "can't activate sqlite3" errors
|
data/Gemfile
CHANGED
|
@@ -4,5 +4,18 @@ source "https://rubygems.org"
|
|
|
4
4
|
|
|
5
5
|
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
6
6
|
|
|
7
|
-
# Specify your gem's dependencies in active_recall.gemspec
|
|
8
7
|
gemspec
|
|
8
|
+
|
|
9
|
+
# TODO: Remove conditional dependency after dropping support for Ruby 3.2
|
|
10
|
+
begin
|
|
11
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
|
12
|
+
if ruby_version >= Gem::Version.new("3.4.0")
|
|
13
|
+
gem "nokogiri", ">= 1.16.2"
|
|
14
|
+
end
|
|
15
|
+
if ruby_version >= Gem::Version.new("4.0.0")
|
|
16
|
+
gem "benchmark"
|
|
17
|
+
gem "ostruct"
|
|
18
|
+
gem "logger"
|
|
19
|
+
end
|
|
20
|
+
rescue
|
|
21
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.0)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
7
|
|
|
@@ -119,7 +119,7 @@ GEM
|
|
|
119
119
|
net-smtp
|
|
120
120
|
marcel (1.0.2)
|
|
121
121
|
mini_mime (1.1.5)
|
|
122
|
-
mini_portile2 (2.8.
|
|
122
|
+
mini_portile2 (2.8.9)
|
|
123
123
|
minitest (5.18.1)
|
|
124
124
|
mutex_m (0.2.0)
|
|
125
125
|
net-imap (0.4.9.1)
|
|
@@ -132,10 +132,14 @@ GEM
|
|
|
132
132
|
net-smtp (0.4.0.1)
|
|
133
133
|
net-protocol
|
|
134
134
|
nio4r (2.7.0)
|
|
135
|
-
nokogiri (1.
|
|
135
|
+
nokogiri (1.18.7)
|
|
136
136
|
mini_portile2 (~> 2.8.2)
|
|
137
137
|
racc (~> 1.4)
|
|
138
|
-
nokogiri (1.
|
|
138
|
+
nokogiri (1.18.7-arm64-darwin)
|
|
139
|
+
racc (~> 1.4)
|
|
140
|
+
nokogiri (1.18.7-x86_64-darwin)
|
|
141
|
+
racc (~> 1.4)
|
|
142
|
+
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
139
143
|
racc (~> 1.4)
|
|
140
144
|
parallel (1.24.0)
|
|
141
145
|
parser (3.3.1.0)
|
|
@@ -219,6 +223,8 @@ GEM
|
|
|
219
223
|
ruby2_keywords (0.0.5)
|
|
220
224
|
sqlite3 (2.5.0)
|
|
221
225
|
mini_portile2 (~> 2.8.0)
|
|
226
|
+
sqlite3 (2.5.0-arm64-darwin)
|
|
227
|
+
sqlite3 (2.5.0-x86_64-darwin)
|
|
222
228
|
sqlite3 (2.5.0-x86_64-linux-gnu)
|
|
223
229
|
standard (1.35.1)
|
|
224
230
|
language_server-protocol (~> 3.17.0.2)
|
|
@@ -244,12 +250,15 @@ GEM
|
|
|
244
250
|
zeitwerk (2.6.12)
|
|
245
251
|
|
|
246
252
|
PLATFORMS
|
|
253
|
+
arm64-darwin
|
|
247
254
|
ruby
|
|
255
|
+
x86_64-darwin
|
|
248
256
|
x86_64-linux
|
|
249
257
|
|
|
250
258
|
DEPENDENCIES
|
|
251
259
|
active_recall!
|
|
252
260
|
appraisal
|
|
261
|
+
nokogiri (>= 1.16.2)
|
|
253
262
|
rails (>= 7.0, < 9.0)
|
|
254
263
|
rake (>= 12.0)
|
|
255
264
|
rdoc
|
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]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Vendored Licenses
|
|
2
|
+
|
|
3
|
+
This file records third-party source code that has been vendored into this
|
|
4
|
+
repository, along with the upstream license terms. ActiveRecall's own license
|
|
5
|
+
is in [LICENSE](LICENSE).
|
|
6
|
+
|
|
7
|
+
## rb-fsrs
|
|
8
|
+
|
|
9
|
+
- **Vendored at:** [`lib/active_recall/algorithms/fsrs/internal.rb`](lib/active_recall/algorithms/fsrs/internal.rb)
|
|
10
|
+
- **Upstream:** https://github.com/open-spaced-repetition/rb-fsrs
|
|
11
|
+
- **Version:** 0.9.0 (commit pulled from the published gem)
|
|
12
|
+
- **License:** MIT
|
|
13
|
+
- **Reason for vendoring:** The published `fsrs` 0.9.0 gem pins
|
|
14
|
+
`activesupport ~> 7.0`, which excludes Rails 8. ActiveRecall supports
|
|
15
|
+
Rails 8, so the code was vendored under `ActiveRecall::FSRS::Internal`.
|
|
16
|
+
The constraint has been widened on rb-fsrs `master`; revisit the
|
|
17
|
+
dependency-vs-vendoring decision once a release with the wider
|
|
18
|
+
constraint ships.
|
|
19
|
+
|
|
20
|
+
### Local divergences from upstream
|
|
21
|
+
|
|
22
|
+
- `Scheduler#schedule_new_state` uses `1.minute` / `5.minutes` /
|
|
23
|
+
`10.minutes` in place of upstream's bare integer arithmetic
|
|
24
|
+
(`now + 60`, `now + (5 * 60)`, `now + (10 * 60)`). With `now` as a
|
|
25
|
+
`DateTime`, the upstream form adds days, not seconds, scheduling new
|
|
26
|
+
cards rated Again/Hard/Good 60 / 300 / 600 days out instead of
|
|
27
|
+
1 / 5 / 10 minutes. Tracks upstream PR
|
|
28
|
+
https://github.com/open-spaced-repetition/rb-fsrs/pull/9.
|
|
29
|
+
|
|
30
|
+
### Upstream license text
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
The MIT License (MIT)
|
|
34
|
+
|
|
35
|
+
Copyright (c) 2024 clayton
|
|
36
|
+
|
|
37
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
38
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
39
|
+
in the Software without restriction, including without limitation the rights
|
|
40
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
41
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
42
|
+
furnished to do so, subject to the following conditions:
|
|
43
|
+
|
|
44
|
+
The above copyright notice and this permission notice shall be included in
|
|
45
|
+
all copies or substantial portions of the Software.
|
|
46
|
+
|
|
47
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
48
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
49
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
50
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
51
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
52
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
53
|
+
THE SOFTWARE.
|
|
54
|
+
```
|
data/gemfiles/rails_7_0.gemfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
active_recall (2.
|
|
4
|
+
active_recall (2.3.0)
|
|
5
5
|
activerecord (>= 7.0, < 9.0)
|
|
6
6
|
activesupport (>= 7.0, < 9.0)
|
|
7
7
|
|
|
@@ -119,6 +119,7 @@ GEM
|
|
|
119
119
|
net-smtp
|
|
120
120
|
marcel (1.0.2)
|
|
121
121
|
mini_mime (1.1.5)
|
|
122
|
+
mini_portile2 (2.8.8)
|
|
122
123
|
minitest (5.21.2)
|
|
123
124
|
mutex_m (0.2.0)
|
|
124
125
|
net-imap (0.4.9.1)
|
|
@@ -131,7 +132,14 @@ GEM
|
|
|
131
132
|
net-smtp (0.4.0.1)
|
|
132
133
|
net-protocol
|
|
133
134
|
nio4r (2.7.0)
|
|
134
|
-
nokogiri (1.
|
|
135
|
+
nokogiri (1.18.7)
|
|
136
|
+
mini_portile2 (~> 2.8.2)
|
|
137
|
+
racc (~> 1.4)
|
|
138
|
+
nokogiri (1.18.7-arm64-darwin)
|
|
139
|
+
racc (~> 1.4)
|
|
140
|
+
nokogiri (1.18.7-x86_64-darwin)
|
|
141
|
+
racc (~> 1.4)
|
|
142
|
+
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
135
143
|
racc (~> 1.4)
|
|
136
144
|
parallel (1.24.0)
|
|
137
145
|
parser (3.3.1.0)
|
|
@@ -216,7 +224,8 @@ GEM
|
|
|
216
224
|
rubocop-ast (>= 1.30.0, < 2.0)
|
|
217
225
|
ruby-progressbar (1.13.0)
|
|
218
226
|
ruby2_keywords (0.0.5)
|
|
219
|
-
sqlite3 (1.7.3
|
|
227
|
+
sqlite3 (1.7.3)
|
|
228
|
+
mini_portile2 (~> 2.8.0)
|
|
220
229
|
standard (1.35.1)
|
|
221
230
|
language_server-protocol (~> 3.17.0.2)
|
|
222
231
|
lint_roller (~> 1.0)
|
|
@@ -242,11 +251,15 @@ GEM
|
|
|
242
251
|
zeitwerk (2.6.12)
|
|
243
252
|
|
|
244
253
|
PLATFORMS
|
|
245
|
-
arm64-darwin
|
|
254
|
+
arm64-darwin
|
|
255
|
+
ruby
|
|
256
|
+
x86_64-darwin
|
|
257
|
+
x86_64-linux
|
|
246
258
|
|
|
247
259
|
DEPENDENCIES
|
|
248
260
|
active_recall!
|
|
249
261
|
appraisal
|
|
262
|
+
nokogiri (>= 1.16.2)
|
|
250
263
|
rails (~> 7.0)
|
|
251
264
|
rake (>= 12.0)
|
|
252
265
|
rdoc
|