acts_as_flashcard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "activerecord"
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.1"
11
+ end
12
+
13
+ group :test do
14
+ gem "shoulda"
15
+ gem "sqlite3-ruby"
16
+ gem "railties"
17
+ end
data/README.rdoc ADDED
@@ -0,0 +1,37 @@
1
+ = acts_as_flashcard
2
+
3
+ acts_as_flashcard is an adaptation of the Anki ( http://ichi2.net/anki/ ) algorithm for ruby on rails application.
4
+
5
+ == Requirements
6
+
7
+ Rails3 (for Rails2 version please check the corresponding branches)
8
+ Has been tested with ruby 1.9.1
9
+
10
+ == Installation
11
+
12
+ gem install acts_as_flashcard
13
+
14
+ == Example
15
+
16
+ class Card < ActiveRecord::Base
17
+ acts_as_flashcard :scope => :user_id
18
+ end
19
+
20
+ card = Card.create
21
+ card.answered!(4)
22
+
23
+ == Contributing to acts_as_flashcard
24
+
25
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
26
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
27
+ * Fork the project
28
+ * Start a feature/bugfix branch
29
+ * Commit and push until you are happy with your contribution
30
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
31
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
32
+
33
+ == Copyright
34
+
35
+ Copyright (c) 2010 Bastien Vaucher. See LICENSE.txt for
36
+ further details.
37
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "acts_as_flashcard"
16
+ gem.homepage = "http://github.com/bastien/acts_as_flashcard"
17
+ gem.license = "GNU"
18
+ gem.summary = "flashcard space repetition algorithm"
19
+ gem.description = "act_as_flashcard is an adaptation of the Anki space repetition algorithm for rails applications"
20
+ gem.email = "bastien.vaucher@gmail.com"
21
+ gem.authors = ["Bastien Vaucher"]
22
+ gem.add_dependency 'activerecord'
23
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
24
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
25
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
26
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rake/testtask'
31
+ Rake::TestTask.new(:test) do |test|
32
+ test.libs << 'lib' << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+
37
+ task :default => :test
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "acts_as_flashcard #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_flashcard}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Bastien Vaucher"]
12
+ s.date = %q{2010-11-26}
13
+ s.description = %q{act_as_flashcard is an adaptation of the Anki space repetition algorithm for rails applications}
14
+ s.email = %q{bastien.vaucher@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "GNU-LICENCE",
20
+ "Gemfile",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "acts_as_flashcard.gemspec",
25
+ "install.rb",
26
+ "lib/acts_as_flashcard.rb",
27
+ "lib/generators/flashcard/USAGE",
28
+ "lib/generators/flashcard/flashcard_generator.rb",
29
+ "lib/generators/flashcard/templates/add_flashcards_fields_migration.rb",
30
+ "lib/generators/flashcard/templates/create_flashcards_migration.rb",
31
+ "tasks/acts_as_flashcard_tasks.rake",
32
+ "test/acts_as_flashcard_test.rb",
33
+ "test/flashcards_migration_generator_test.rb",
34
+ "test/test_helper.rb",
35
+ "uninstall.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/bastien/acts_as_flashcard}
38
+ s.licenses = ["GNU"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.7}
41
+ s.summary = %q{flashcard space repetition algorithm}
42
+ s.test_files = [
43
+ "test/acts_as_flashcard_test.rb",
44
+ "test/flashcards_migration_generator_test.rb",
45
+ "test/test_helper.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
54
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
55
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
56
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<activerecord>, [">= 0"])
59
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
61
+ s.add_dependency(%q<activerecord>, [">= 0"])
62
+ end
63
+ else
64
+ s.add_dependency(%q<activerecord>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
67
+ s.add_dependency(%q<activerecord>, [">= 0"])
68
+ end
69
+ end
70
+
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,201 @@
1
+ #ActsAsFlashcard
2
+ module Flashcard #:nodoc:
3
+ module Base #:nodoc:
4
+
5
+ def self.included(base) #:nodoc:
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ MAX_SCHEDULE_TIME = 1825
10
+ INITIAL_FACTOR = 2.5
11
+ EASY_INTERVAL_MIN = 7
12
+ EASY_INTERVAL_MAX = 9
13
+ MID_INTERVAL_MIN = 3
14
+ MID_INTERVAL_MAX = 5
15
+ HARD_INTERVAL_MIN = 0.333
16
+ HARD_INTERVAL_MAX = 0.5
17
+ DELAY = 600
18
+ FACTOR_FOUR = 1.3
19
+
20
+ # successive :integer default(0)
21
+ # repetitions :integer default(0)
22
+ # yes_count :integer default(0)
23
+ # no_count :integer default(0)
24
+ # factor :float default(2.5)
25
+ # last_factor :float default(2.5)
26
+ # interval :integer default(0)
27
+ # last_interval :float default(0.0)
28
+ # due :datetime
29
+ # combined_due :integer default(0)
30
+
31
+ module ClassMethods
32
+
33
+ def acts_as_flashcard(options = {})
34
+ configuration = {:scope => "1 = 1"}
35
+ configuration.update(options) if options.is_a?(Hash)
36
+
37
+ configuration[:scope] = "#{configuration[:scope]}_id".to_sym if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
38
+
39
+ if configuration[:scope].is_a?(Symbol)
40
+ scope_condition_method = %(
41
+ def scope_condition
42
+ if #{configuration[:scope].to_s}.nil?
43
+ "#{configuration[:scope].to_s} IS NULL"
44
+ else
45
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
46
+ end
47
+ end
48
+ )
49
+ else
50
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
51
+ end
52
+
53
+ class_eval <<-EOV
54
+ include Flashcard::Base::InstanceMethods
55
+
56
+ def acts_as_flashcard_class
57
+ ::#{self.name}
58
+ end
59
+
60
+ #{scope_condition_method}
61
+
62
+ before_create :initialize_due_date
63
+ EOV
64
+ end
65
+ end
66
+
67
+ module InstanceMethods
68
+
69
+ # ease: 0 - 4
70
+ #
71
+ def answered!(ease)
72
+ increment_repetitions(ease)
73
+ last_delay_secs = (Time.now - self.combined_due).to_f
74
+ last_delay = last_delay_secs / 86400.0
75
+ self.interval = next_interval(ease) unless last_delay >= 0 # keep last interval if reviewing early
76
+ last_due = self.due
77
+ self.due = next_due(ease)
78
+ update_factor(ease) if last_delay >= 0
79
+ min_of_other_cards = acts_as_flashcard_class.minimum('interval', :conditions => "#{scope_condition} AND id != #{self.id}")
80
+ space = min_of_other_cards.nil? ? 0 : [min_of_other_cards, self.interval].min
81
+ space = space * 0.1.day
82
+ space = [space, 60].max
83
+ space = Time.now + space
84
+ self.combined_due = [space, due].max
85
+ save
86
+ end
87
+
88
+ private
89
+
90
+ # Set the due date to today when the card is created
91
+ #
92
+ def initialize_due_date
93
+ self.due = Time.now
94
+ end
95
+
96
+ # Increments the count of time this card has been reviewed,
97
+ # How many times it hasn't been answered wrong successively
98
+ # as well as the amount of time it has been answered right and wrong
99
+ #
100
+ def increment_repetitions(ease)
101
+ self.repetitions += 1
102
+ if ease > 1
103
+ self.successive += 1
104
+ else
105
+ self.successive = 0
106
+ end
107
+ if self.repetitions > 1
108
+ if ease < 2
109
+ self.no_count += 1
110
+ else
111
+ self.yes_count += 1
112
+ end
113
+ end
114
+ end
115
+
116
+ def delay
117
+ return 0 if repetitions == 0
118
+ if self.combined_due <= Time.now.to_f
119
+ return Time.now - self.due
120
+ else
121
+ return Time.now - self.combined_due
122
+ end
123
+ end
124
+
125
+ def next_interval(ease)
126
+ current_delay = delay.to_i
127
+ current_interval = self.interval
128
+ if current_delay < 0 && self.successive > 0
129
+ current_interval = [self.last_interval, self.interval + current_delay].max
130
+ if current_interval < MID_INTERVAL_MIN
131
+ current_interval = 0
132
+ end
133
+ current_delay = 0
134
+ end
135
+ if ease == 1
136
+ current_interval *= DELAY
137
+ current_interval = 0 if current_interval < HARD_INTERVAL_MIN
138
+ elsif current_interval == 0
139
+ case ease
140
+ when 2 then current_interval = rand(HARD_INTERVAL_MAX - HARD_INTERVAL_MIN) + HARD_INTERVAL_MIN
141
+ when 3 then current_interval = rand(MID_INTERVAL_MAX - MID_INTERVAL_MIN) + MID_INTERVAL_MIN
142
+ when 4 then current_interval = rand(EASY_INTERVAL_MAX - EASY_INTERVAL_MIN) + EASY_INTERVAL_MIN
143
+ end
144
+ else
145
+ if current_interval < HARD_INTERVAL_MAX && current_interval > 0.166
146
+ mid = MID_INTERVAL_MIN + MID_INTERVAL_MAX / 2
147
+ current_interval *= (mid / current_interval / self.factor)
148
+ case ease
149
+ when 2 then current_interval = (current_interval + current_delay/4) * 1.2
150
+ when 3 then current_interval = (current_interval + current_delay/2) * self.factor
151
+ when 4 then current_interval = (current_interval + current_delay) * self.factor * FACTOR_FOUR
152
+ end
153
+ fuzz = (rand(10) + 95).to_f / 100.0
154
+ current_interval *= fuzz
155
+ end
156
+ end
157
+ return [MAX_SCHEDULE_TIME, current_interval].min
158
+ end
159
+
160
+ # Average factor among all the cards
161
+ #
162
+ def average_factor
163
+ avg_factor = acts_as_flashcard_class.average("factor", :conditions => "#{scope_condition} AND id != #{self.id}")
164
+ return avg_factor ||= INITIAL_FACTOR
165
+ end
166
+
167
+ def update_factor(ease)
168
+ self.last_factor = self.factor
169
+ if repetitions == 0
170
+ self.factor = averageFactor
171
+ end
172
+ if being_learnt? && ([0, 1, 2].include?(ease))
173
+ self.factor -= 0.20 if (self.successive > 0) && (ease != 2)
174
+ else
175
+ case ease
176
+ when 0..1 then self.factor -= 0.20
177
+ when 2 then self.factor -= 0.15
178
+ when 4 then self.factor += 0.10
179
+ end
180
+ end
181
+ self.factor = [1.3, self.factor].max
182
+ end
183
+
184
+ def next_due(ease)
185
+ if ease == 1
186
+ return Time.now + DELAY
187
+ else
188
+ return Time.now + self.interval.day
189
+ end
190
+ end
191
+
192
+ def being_learnt?
193
+ self.interval < EASY_INTERVAL_MIN
194
+ end
195
+
196
+ end
197
+
198
+ end
199
+ end
200
+
201
+ ::ActiveRecord::Base.send :include, Flashcard::Base
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Adds a migration file for the flashcard model
3
+
4
+ Usage:
5
+ script/generate flashcard FLASHCARDMODEL
@@ -0,0 +1,25 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class FlashcardGenerator < Rails::Generators::NamedBase
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ def self.next_migration_number(dirname)
12
+ Time.now.strftime("%Y%m%d%H%m%s")
13
+ end
14
+
15
+ def install_flashcards
16
+ if File.exists?("#{destination_root}/db/schema.rb") &&
17
+ File.read("#{destination_root}/db/schema.rb") =~ /create_table \"#{table_name}\"/
18
+ migration_template("add_flashcards_fields_migration.rb","db/migrate/add_flashcards_fields.rb")
19
+ else
20
+ migration_template("create_flashcards_migration.rb","db/migrate/create_flashcards.rb")
21
+ end
22
+ end
23
+
24
+
25
+ end
@@ -0,0 +1,27 @@
1
+ class <%= @migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :<%= table_name %>, :successive, :integer, :default => 0
4
+ add_column :<%= table_name %>, :repetitions, :integer, :default => 0
5
+ add_column :<%= table_name %>, :yes_count, :integer, :default => 0
6
+ add_column :<%= table_name %>, :no_count, :integer, :default => 0
7
+ add_column :<%= table_name %>, :factor, :float, :default => 2.5
8
+ add_column :<%= table_name %>, :last_factor, :float, :default => 2.5
9
+ add_column :<%= table_name %>, :interval, :integer, :default => 0
10
+ add_column :<%= table_name %>, :last_interval,:float, :default => 0
11
+ add_column :<%= table_name %>, :due, :datetime
12
+ add_column :<%= table_name %>, :combined_due, :integer, :default => 0
13
+ end
14
+
15
+ def self.down
16
+ remove_column :<%= table_name %>, :successive
17
+ remove_column :<%= table_name %>, :repetitions
18
+ remove_column :<%= table_name %>, :yes_count
19
+ remove_column :<%= table_name %>, :no_count
20
+ remove_column :<%= table_name %>, :factor
21
+ remove_column :<%= table_name %>, :last_factor
22
+ remove_column :<%= table_name %>, :interval
23
+ remove_column :<%= table_name %>, :last_interval
24
+ remove_column :<%= table_name %>, :due
25
+ remove_column :<%= table_name %>, :combined_due
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ class <%= @migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table "<%= table_name %>" do |t|
4
+ t.column :successive, :integer, :default => 0
5
+ t.column :repetitions, :integer, :default => 0
6
+ t.column :yes_count, :integer, :default => 0
7
+ t.column :no_count, :integer, :default => 0
8
+ t.column :factor, :float, :default => 2.5
9
+ t.column :last_factor, :float, :default => 2.5
10
+ t.column :interval, :integer, :default => 0
11
+ t.column :last_interval,:float, :default => 0
12
+ t.column :due, :datetime
13
+ t.column :combined_due, :integer, :default => 0
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table "<%= table_name %>"
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_flashcard do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,130 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+ #require File.dirname(__FILE__) + "/../init"
5
+
6
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7
+ ActiveRecord::Migration.verbose = false
8
+
9
+ def setup_db
10
+ ActiveRecord::Schema.define(:version => 1) do
11
+ create_table :cards do |t|
12
+ t.column :successive, :integer, :default => 0
13
+ t.column :repetitions, :integer, :default => 0
14
+ t.column :yes_count, :integer, :default => 0
15
+ t.column :no_count, :integer, :default => 0
16
+ t.column :factor, :float, :default => 2.5
17
+ t.column :last_factor, :float, :default => 2.5
18
+ t.column :interval, :integer, :default => 0
19
+ t.column :last_interval, :float, :default => 0
20
+ t.column :due, :datetime
21
+ t.column :combined_due, :integer, :default => 0
22
+ t.column :created_at, :datetime
23
+ t.column :updated_at, :datetime
24
+ end
25
+ end
26
+ end
27
+
28
+ def teardown_db
29
+ ActiveRecord::Base.connection.tables.each do |table|
30
+ ActiveRecord::Base.connection.drop_table(table)
31
+ end
32
+ end
33
+
34
+ class Card < ActiveRecord::Base
35
+ acts_as_flashcard
36
+ end
37
+
38
+
39
+ class ActsAsFlashcardTest < Test::Unit::TestCase
40
+
41
+ def setup
42
+ setup_db
43
+ end
44
+
45
+ def teardown
46
+ teardown_db
47
+ end
48
+
49
+ def test_due_date_updated_after_correct_answer
50
+ card = Card.create
51
+ due_before = card.due
52
+ card.answered!(4)
53
+ assert_equal (card.due > due_before), true
54
+ end
55
+
56
+ def test_factor_should_increase_when_answered_right
57
+ card = Card.create(:repetitions => 5)
58
+ factor_before = card.factor
59
+ card.answered!(4)
60
+ assert_equal card.factor, factor_before + 0.10
61
+ end
62
+
63
+ def test_factor_should_decrease_when_answered_wrong_if_not_learnt
64
+ card = Card.create(:interval => 7.5)
65
+ factor_before = card.factor
66
+ card.answered!(1)
67
+ assert_equal card.factor, factor_before - 0.20
68
+ end
69
+
70
+ def test_should_return_the_average_actor
71
+ [1,2,3,4,5].each {|i| Card.create(:factor => i)}
72
+ card = Card.create
73
+ avg = card.__send__(:average_factor)
74
+ assert_equal avg, 3
75
+ end
76
+
77
+ def test_when_answered_0_no_count_should_increase
78
+ card = Card.create(:repetitions => 3, :successive => 2, :no_count => 1, :yes_count => 3)
79
+ card.answered!(0)
80
+ assert_equal card.repetitions, 4
81
+ assert_equal card.successive, 0
82
+ assert_equal card.no_count, 2
83
+ assert_equal card.yes_count, 3
84
+ end
85
+
86
+ def test_when_answered_2_yes_count_should_increase
87
+ card = Card.create(:repetitions => 3, :successive => 2, :no_count => 1, :yes_count => 3)
88
+ card.answered!(2)
89
+ assert_equal card.repetitions, 4
90
+ assert_equal card.successive, 3
91
+ assert_equal card.no_count, 1
92
+ assert_equal card.yes_count, 4
93
+ end
94
+
95
+ def test_update_increase_combined_due
96
+ card = Card.create()
97
+ combined_due_before = card.combined_due
98
+ card.answered!(2)
99
+ assert card.combined_due > combined_due_before
100
+ combined_due_before = card.combined_due
101
+ card.answered!(4)
102
+ assert card.combined_due > combined_due_before
103
+ combined_due_before = card.combined_due
104
+ card.answered!(3)
105
+ assert card.combined_due < combined_due_before
106
+ combined_due_before = card.combined_due
107
+ card.answered!(2)
108
+ assert card.combined_due < combined_due_before
109
+ end
110
+
111
+ def test_increase_interval_when_right
112
+ card = Card.create()
113
+ card.answered!(2)
114
+ card.answered!(2)
115
+ interval_before = card.interval
116
+ card.answered!(4)
117
+ card.answered!(4)
118
+ assert card.interval > interval_before
119
+ end
120
+
121
+ def test_decrease_interval_when_wrong
122
+ card = Card.create()
123
+ card.answered!(2)
124
+ card.answered!(4)
125
+ interval_before = card.interval
126
+ card.answered!(1)
127
+ assert card.interval >= interval_before
128
+ end
129
+
130
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+ #require 'rails/generators'
3
+ require 'generators/flashcard/flashcard_generator'
4
+
5
+ class MigrationGeneratorTest < Test::Unit::TestCase
6
+
7
+ def test_install_flashcard
8
+ FlashcardGenerator.start(["some_name_nobody_is_likely_to_ever_use_in_a_real_migration"], :destination_root => @destination)
9
+ new_file = (file_list - @original_files).first
10
+ assert_match /create_flashcards/, new_file
11
+ assert_match /create_table :some_name_nobody_is_likely_to_ever_use_in_a_real_migrations do |t|/, File.read(new_file)
12
+ end
13
+
14
+ def test_add_flashcard_fields
15
+ create_schema_file
16
+ FlashcardGenerator.start(["some_name_nobody_is_likely_to_ever_use_in_a_real_migration"], :destination_root => @destination)
17
+ new_file = (file_list - @original_files).first
18
+ assert_match /add_flashcards_fields/, new_file
19
+ assert_match /add_column :some_name_nobody_is_likely_to_ever_use_in_a_real_migrations/, File.read(new_file)
20
+ end
21
+
22
+ def setup
23
+ @destination = File.join('tmp', 'test_app')
24
+ @source = FlashcardGenerator.source_root
25
+ @original_files = file_list
26
+ end
27
+
28
+ def teardown
29
+ FileUtils.rm_rf(@destination)
30
+ end
31
+
32
+ private
33
+
34
+ def file_list
35
+ Dir.glob(File.join(@destination, "db", "migrate", "*"))
36
+ end
37
+
38
+ def create_schema_file
39
+ FileUtils.mkdir_p(@destination)
40
+ Dir.mkdir(File.join(@destination, "db"))
41
+ File.open(File.join(@destination, "db", "schema.rb"), 'w') do |f|
42
+ f.write("ActiveRecord::Schema.define(:version => 20100201214414) do\n")
43
+ f.write("\tcreate_table \"some_name_nobody_is_likely_to_ever_use_in_a_real_migrations\", :force => true do |t|\n")
44
+ f.write("\t\tt.integer \"user_id\"\n")
45
+ f.write("\tend\nend")
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ # require 'shoulda'
12
+ require 'active_record'
13
+ require 'sqlite3'
14
+
15
+
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
17
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
+ require 'acts_as_flashcard'
19
+
20
+ class Test::Unit::TestCase
21
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here