active_recall 1.8.6 → 2.0.1

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