active_recall 2.1.0 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1145dd0124d31f1cd1d200170e3956b20818e73bd2d1fedb4ceacf4f82a8f4ba
4
- data.tar.gz: b4b8e2c1d1edd66cc0c756200b525aec806468c7f95626c257bd95f369d56037
3
+ metadata.gz: b09e91a09c75f943e1bb2ced582d4520674053f5504f8e14fcfe370babbf4300
4
+ data.tar.gz: 9acaaad5775d5ff38694260e9d66983f97cd614ff5c52fde3bc67c7e13d14b01
5
5
  SHA512:
6
- metadata.gz: e94584e1041b5f695ab3ec0d24a87a7c3a4abf200fb11c7951917f98cbd84f9e24225b52a6c45d393fe31beb76e08a9ac683266cd191f7ec5cacd78644b8ed68
7
- data.tar.gz: 6979b3ac85b8cd12751566161bfa2d55ca5832fc82693a92469f3844ec481d86c4bda05fc709fcbb2ea8f3dbc84959a8d342a683ce6e936a05ab235c278a66df
6
+ metadata.gz: 6fffb380767376c2ad2b4bec163b508b2a32a03a33b05eebe755e80e6b0c825c1fd2a53133ae33e2582379e55329ef7a8b32dfbc3565a4fd4bca6ca2a93c994e
7
+ data.tar.gz: 982a9a742fb97e0cc9f0db89d6664f253c7f4534929b030d3ce78d9c3d164b08e2087cc31da79fbda4fb69b2e58e2bd05362a436d37743bebab574a1c529a78a
@@ -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: true
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.3.0
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.1.0)
4
+ active_recall (2.2.0)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
7
 
@@ -132,10 +132,10 @@ GEM
132
132
  net-smtp (0.4.0.1)
133
133
  net-protocol
134
134
  nio4r (2.7.0)
135
- nokogiri (1.16.0)
135
+ nokogiri (1.18.7)
136
136
  mini_portile2 (~> 2.8.2)
137
137
  racc (~> 1.4)
138
- nokogiri (1.16.0-x86_64-linux)
138
+ nokogiri (1.18.7-x86_64-linux-gnu)
139
139
  racc (~> 1.4)
140
140
  parallel (1.24.0)
141
141
  parser (3.3.1.0)
@@ -250,6 +250,7 @@ PLATFORMS
250
250
  DEPENDENCIES
251
251
  active_recall!
252
252
  appraisal
253
+ nokogiri (>= 1.16.2)
253
254
  rails (>= 7.0, < 9.0)
254
255
  rake (>= 12.0)
255
256
  rdoc
@@ -2,6 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ gem "nokogiri", ">= 1.16.2"
5
6
  gem "rails", "~> 7.0"
6
7
  gem "sqlite3", "~> 1.4"
7
8
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.1.0)
4
+ active_recall (2.2.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,7 @@ GEM
131
132
  net-smtp (0.4.0.1)
132
133
  net-protocol
133
134
  nio4r (2.7.0)
134
- nokogiri (1.16.0-arm64-darwin)
135
+ nokogiri (1.18.7-arm64-darwin)
135
136
  racc (~> 1.4)
136
137
  parallel (1.24.0)
137
138
  parser (3.3.1.0)
@@ -216,7 +217,8 @@ GEM
216
217
  rubocop-ast (>= 1.30.0, < 2.0)
217
218
  ruby-progressbar (1.13.0)
218
219
  ruby2_keywords (0.0.5)
219
- sqlite3 (1.7.3-arm64-darwin)
220
+ sqlite3 (1.7.3)
221
+ mini_portile2 (~> 2.8.0)
220
222
  standard (1.35.1)
221
223
  language_server-protocol (~> 3.17.0.2)
222
224
  lint_roller (~> 1.0)
@@ -243,10 +245,13 @@ GEM
243
245
 
244
246
  PLATFORMS
245
247
  arm64-darwin-23
248
+ arm64-darwin-24
249
+ arm64-darwin-25
246
250
 
247
251
  DEPENDENCIES
248
252
  active_recall!
249
253
  appraisal
254
+ nokogiri (>= 1.16.2)
250
255
  rails (~> 7.0)
251
256
  rake (>= 12.0)
252
257
  rdoc
@@ -2,6 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ gem "nokogiri", ">= 1.16.2"
5
6
  gem "rails", "~> 7.1"
6
7
  gem "sqlite3", "~> 1.4"
7
8
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.1.0)
4
+ active_recall (2.2.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,7 @@ GEM
131
132
  net-smtp (0.4.0.1)
132
133
  net-protocol
133
134
  nio4r (2.7.0)
134
- nokogiri (1.16.0-arm64-darwin)
135
+ nokogiri (1.18.7-arm64-darwin)
135
136
  racc (~> 1.4)
136
137
  parallel (1.24.0)
137
138
  parser (3.3.1.0)
@@ -216,7 +217,8 @@ GEM
216
217
  rubocop-ast (>= 1.30.0, < 2.0)
217
218
  ruby-progressbar (1.13.0)
218
219
  ruby2_keywords (0.0.5)
219
- sqlite3 (1.7.3-arm64-darwin)
220
+ sqlite3 (1.7.3)
221
+ mini_portile2 (~> 2.8.0)
220
222
  standard (1.35.1)
221
223
  language_server-protocol (~> 3.17.0.2)
222
224
  lint_roller (~> 1.0)
@@ -243,10 +245,13 @@ GEM
243
245
 
244
246
  PLATFORMS
245
247
  arm64-darwin-23
248
+ arm64-darwin-24
249
+ arm64-darwin-25
246
250
 
247
251
  DEPENDENCIES
248
252
  active_recall!
249
253
  appraisal
254
+ nokogiri (>= 1.16.2)
250
255
  rails (~> 7.1)
251
256
  rake (>= 12.0)
252
257
  rdoc
@@ -2,6 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ gem "nokogiri", ">= 1.16.2"
5
6
  gem "rails", "~> 8.0"
6
7
  gem "sqlite3", ">= 2.1"
7
8
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.1.0)
4
+ active_recall (2.2.0)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
7
 
@@ -117,7 +117,6 @@ GEM
117
117
  net-smtp
118
118
  marcel (1.0.4)
119
119
  mini_mime (1.1.5)
120
- mini_portile2 (2.8.5)
121
120
  minitest (5.25.4)
122
121
  net-imap (0.5.4)
123
122
  date
@@ -212,8 +211,7 @@ GEM
212
211
  rubocop-ast (>= 1.31.1, < 2.0)
213
212
  ruby-progressbar (1.13.0)
214
213
  securerandom (0.4.1)
215
- sqlite3 (2.5.0)
216
- mini_portile2 (~> 2.8.0)
214
+ sqlite3 (2.5.0-arm64-darwin)
217
215
  standard (1.43.0)
218
216
  language_server-protocol (~> 3.17.0.2)
219
217
  lint_roller (~> 1.0)
@@ -243,10 +241,13 @@ GEM
243
241
 
244
242
  PLATFORMS
245
243
  arm64-darwin-23
244
+ arm64-darwin-24
245
+ arm64-darwin-25
246
246
 
247
247
  DEPENDENCIES
248
248
  active_recall!
249
249
  appraisal
250
+ nokogiri (>= 1.16.2)
250
251
  rails (~> 8.0)
251
252
  rake (>= 12.0)
252
253
  rdoc
@@ -63,11 +63,10 @@ module ActiveRecall
63
63
  SEQUENCE = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765].freeze
64
64
 
65
65
  def fibonacci_number_at(index)
66
- if (0...SEQUENCE.length).cover?(index)
67
- SEQUENCE[index]
68
- else
69
- fibonacci_number_at(index - 1) + fibonacci_number_at(index - 2)
70
- end
66
+ return SEQUENCE[index] if (0...SEQUENCE.length).cover?(index)
67
+
68
+ @fibonacci_cache ||= {}
69
+ @fibonacci_cache[index] ||= fibonacci_number_at(index - 1) + fibonacci_number_at(index - 2)
71
70
  end
72
71
 
73
72
  def next_review
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecall
2
4
  class SM2
3
5
  MIN_EASINESS_FACTOR = 1.3
@@ -38,7 +40,7 @@ module ActiveRecall
38
40
  def score
39
41
  raise "Grade must be between 0-5!" unless GRADES.include?(@grade)
40
42
  old_ef = @easiness_factor
41
- update_easiness_factor
43
+ update_easiness_factor if @grade >= 3
42
44
  update_repetition_and_interval(old_ef)
43
45
 
44
46
  {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecall
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.0"
5
5
  end
data/standard.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  fix: true
2
2
  parallel: true
3
- ruby_version: 3.0
3
+ ruby_version: 3.2
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_recall
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gravina
8
8
  - Jayson Virissimo
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2024-12-25 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: appraisal
@@ -168,6 +167,7 @@ files:
168
167
  - ".rspec"
169
168
  - ".tool-versions"
170
169
  - Appraisals
170
+ - CLAUDE.md
171
171
  - Gemfile
172
172
  - Gemfile.lock
173
173
  - LICENSE
@@ -209,7 +209,6 @@ licenses:
209
209
  - MIT
210
210
  metadata:
211
211
  allowed_push_host: https://rubygems.org/
212
- post_install_message:
213
212
  rdoc_options: []
214
213
  require_paths:
215
214
  - lib
@@ -224,8 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
223
  - !ruby/object:Gem::Version
225
224
  version: '0'
226
225
  requirements: []
227
- rubygems_version: 3.5.3
228
- signing_key:
226
+ rubygems_version: 3.6.9
229
227
  specification_version: 4
230
228
  summary: A spaced-repetition system
231
229
  test_files: []