active_recall 1.8.6 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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