mongoid-haystack 1.0.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.
Files changed (32) hide show
  1. data/Rakefile +446 -0
  2. data/lib/app/models/mongoid/haystack/count.rb +1 -0
  3. data/lib/app/models/mongoid/haystack/index.rb +1 -0
  4. data/lib/app/models/mongoid/haystack/sequence.rb +1 -0
  5. data/lib/app/models/mongoid/haystack/token.rb +1 -0
  6. data/lib/mongoid-haystack.rb +79 -0
  7. data/lib/mongoid-haystack/count.rb +28 -0
  8. data/lib/mongoid-haystack/index.rb +165 -0
  9. data/lib/mongoid-haystack/search.rb +96 -0
  10. data/lib/mongoid-haystack/sequence.rb +55 -0
  11. data/lib/mongoid-haystack/stemming.rb +79 -0
  12. data/lib/mongoid-haystack/stemming/stopwords/english.txt +32 -0
  13. data/lib/mongoid-haystack/stemming/stopwords/extended_english.txt +216 -0
  14. data/lib/mongoid-haystack/stemming/stopwords/full_danish.txt +94 -0
  15. data/lib/mongoid-haystack/stemming/stopwords/full_dutch.txt +101 -0
  16. data/lib/mongoid-haystack/stemming/stopwords/full_english.txt +174 -0
  17. data/lib/mongoid-haystack/stemming/stopwords/full_finnish.txt +0 -0
  18. data/lib/mongoid-haystack/stemming/stopwords/full_french.txt +155 -0
  19. data/lib/mongoid-haystack/stemming/stopwords/full_german.txt +231 -0
  20. data/lib/mongoid-haystack/stemming/stopwords/full_italian.txt +279 -0
  21. data/lib/mongoid-haystack/stemming/stopwords/full_norwegian.txt +176 -0
  22. data/lib/mongoid-haystack/stemming/stopwords/full_portuguese.txt +203 -0
  23. data/lib/mongoid-haystack/stemming/stopwords/full_russian.txt +101 -0
  24. data/lib/mongoid-haystack/stemming/stopwords/full_russiankoi8_r.txt +101 -0
  25. data/lib/mongoid-haystack/stemming/stopwords/full_spanish.txt +313 -0
  26. data/lib/mongoid-haystack/token.rb +71 -0
  27. data/lib/mongoid-haystack/util.rb +67 -0
  28. data/mongoid-haystack.gemspec +73 -0
  29. data/test/helper.rb +28 -0
  30. data/test/mongoid-haystack_test.rb +119 -0
  31. data/test/testing.rb +196 -0
  32. metadata +123 -0
@@ -0,0 +1,67 @@
1
+ module Mongoid
2
+ module Haystack
3
+ module Util
4
+ def models
5
+ [
6
+ Mongoid::Haystack::Token,
7
+ Mongoid::Haystack::Index,
8
+ Mongoid::Haystack::Count,
9
+ Mongoid::Haystack::Sequence
10
+ ]
11
+ end
12
+
13
+ def reset!
14
+ models.each do |model|
15
+ begin
16
+ model.collection.indexes.drop
17
+ rescue Object => e
18
+ end
19
+
20
+ begin
21
+ model.collection.drop
22
+ rescue Object => e
23
+ end
24
+
25
+ begin
26
+ model.create_indexes
27
+ rescue Object => e
28
+ end
29
+ end
30
+ end
31
+
32
+ def destroy_all
33
+ models.map{|model| model.destroy_all}
34
+ end
35
+
36
+ def stem(*args, &block)
37
+ Stemming.stem(*args, &block)
38
+ end
39
+
40
+ def find_or_create(finder, creator)
41
+ doc = finder.call()
42
+ return doc if doc
43
+
44
+ n, max = 0, 2
45
+
46
+ begin
47
+ creator.call()
48
+ rescue Object => e
49
+ n += 1
50
+ raise if n > max
51
+ sleep(rand(0.1))
52
+ finder.call() or retry
53
+ end
54
+ end
55
+
56
+ def connect!
57
+ Mongoid.configure do |config|
58
+ config.connect_to('mongoid-haystack')
59
+ end
60
+ end
61
+
62
+ extend Util
63
+ end
64
+
65
+ extend Util
66
+ end
67
+ end
@@ -0,0 +1,73 @@
1
+ ## mongoid-haystack.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "mongoid-haystack"
6
+ spec.version = "1.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "mongoid-haystack"
9
+ spec.description = "a mongoid 3 zero-config, zero-integration, POLS pure mongo fulltext solution"
10
+
11
+ spec.files =
12
+ ["Rakefile",
13
+ "lib",
14
+ "lib/app",
15
+ "lib/app/models",
16
+ "lib/app/models/mongoid",
17
+ "lib/app/models/mongoid/haystack",
18
+ "lib/app/models/mongoid/haystack/count.rb",
19
+ "lib/app/models/mongoid/haystack/index.rb",
20
+ "lib/app/models/mongoid/haystack/sequence.rb",
21
+ "lib/app/models/mongoid/haystack/token.rb",
22
+ "lib/mongoid-haystack",
23
+ "lib/mongoid-haystack.rb",
24
+ "lib/mongoid-haystack/count.rb",
25
+ "lib/mongoid-haystack/index.rb",
26
+ "lib/mongoid-haystack/search.rb",
27
+ "lib/mongoid-haystack/sequence.rb",
28
+ "lib/mongoid-haystack/stemming",
29
+ "lib/mongoid-haystack/stemming.rb",
30
+ "lib/mongoid-haystack/stemming/stopwords",
31
+ "lib/mongoid-haystack/stemming/stopwords/english.txt",
32
+ "lib/mongoid-haystack/stemming/stopwords/extended_english.txt",
33
+ "lib/mongoid-haystack/stemming/stopwords/full_danish.txt",
34
+ "lib/mongoid-haystack/stemming/stopwords/full_dutch.txt",
35
+ "lib/mongoid-haystack/stemming/stopwords/full_english.txt",
36
+ "lib/mongoid-haystack/stemming/stopwords/full_finnish.txt",
37
+ "lib/mongoid-haystack/stemming/stopwords/full_french.txt",
38
+ "lib/mongoid-haystack/stemming/stopwords/full_german.txt",
39
+ "lib/mongoid-haystack/stemming/stopwords/full_italian.txt",
40
+ "lib/mongoid-haystack/stemming/stopwords/full_norwegian.txt",
41
+ "lib/mongoid-haystack/stemming/stopwords/full_portuguese.txt",
42
+ "lib/mongoid-haystack/stemming/stopwords/full_russian.txt",
43
+ "lib/mongoid-haystack/stemming/stopwords/full_russiankoi8_r.txt",
44
+ "lib/mongoid-haystack/stemming/stopwords/full_spanish.txt",
45
+ "lib/mongoid-haystack/token.rb",
46
+ "lib/mongoid-haystack/util.rb",
47
+ "mongoid-haystack.gemspec",
48
+ "test",
49
+ "test/helper.rb",
50
+ "test/mongoid-haystack_test.rb",
51
+ "test/testing.rb"]
52
+
53
+ spec.executables = []
54
+
55
+ spec.require_path = "lib"
56
+
57
+ spec.test_files = nil
58
+
59
+
60
+ spec.add_dependency(*["mongoid", "~> 3.0"])
61
+
62
+ spec.add_dependency(*["map", "~> 6.2"])
63
+
64
+ spec.add_dependency(*["fattr", "~> 2.2"])
65
+
66
+
67
+ spec.extensions.push(*[])
68
+
69
+ spec.rubyforge_project = "codeforpeople"
70
+ spec.author = "Ara T. Howard"
71
+ spec.email = "ara.t.howard@gmail.com"
72
+ spec.homepage = "https://github.com/ahoward/mongoid-haystack"
73
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # this triggers mongoid to load rails...
4
+ # module Rails; end
5
+
6
+ require_relative 'testing'
7
+ require_relative '../lib/mongoid-haystack.rb'
8
+
9
+ Mongoid::Haystack.connect!
10
+
11
+ class A
12
+ include Mongoid::Document
13
+ field(:content, :type => String)
14
+ def to_s; content; end
15
+ end
16
+
17
+ class B
18
+ include Mongoid::Document
19
+ field(:content, :type => String)
20
+ def to_s; content; end
21
+ end
22
+
23
+ class C
24
+ include Mongoid::Document
25
+ field(:content, :type => String)
26
+ def to_s; content; end
27
+ end
28
+
@@ -0,0 +1,119 @@
1
+ require_relative 'helper'
2
+
3
+ Testing Mongoid::Haystack do
4
+ ##
5
+ #
6
+ Mongoid::Haystack.reset!
7
+
8
+ setup do
9
+ [A, B, C].map{|m| m.destroy_all}
10
+ Mongoid::Haystack.destroy_all
11
+ end
12
+
13
+ ##
14
+ #
15
+ testing 'that models can, at minimum, be indexed and searched' do
16
+ a = A.create!(:content => 'dog')
17
+ b = B.create!(:content => 'cat')
18
+
19
+ assert{ Mongoid::Haystack.index(a) }
20
+ assert{ Mongoid::Haystack.index(b) }
21
+
22
+ assert{ Mongoid::Haystack.search('dog').map(&:model) == [a] }
23
+ assert{ Mongoid::Haystack.search('cat').map(&:model) == [b] }
24
+ end
25
+
26
+ ##
27
+ #
28
+ testing 'that word occurance affects the sort' do
29
+ a = A.create!(:content => 'dog')
30
+ b = A.create!(:content => 'dog dog')
31
+ c = A.create!(:content => 'dog dog dog')
32
+
33
+ assert{ Mongoid::Haystack.index(A) }
34
+ assert{ Mongoid::Haystack.search('dog').map(&:model) == [c, b, a] }
35
+ end
36
+
37
+ ##
38
+ #
39
+ testing 'that rare words float to the front of the results' do
40
+ a = A.create!(:content => 'dog')
41
+ b = A.create!(:content => 'dog dog')
42
+ c = A.create!(:content => 'dog dog dog')
43
+ d = A.create!(:content => 'dog dog dog cat')
44
+
45
+ assert{ Mongoid::Haystack.index(A) }
46
+ assert{ Mongoid::Haystack.search('cat dog').map(&:model) == [d, c, b, a] }
47
+ end
48
+
49
+ ##
50
+ #
51
+ testing 'that basic stemming can be performed' do
52
+ assert{ Mongoid::Haystack.stem('dogs cats') == %w[ dog cat ] }
53
+ end
54
+
55
+ testing 'that words are stemmed when they are indexed' do
56
+ a = A.create!(:content => 'dog')
57
+ b = A.create!(:content => 'dogs')
58
+ c = A.create!(:content => 'dogen')
59
+
60
+ assert{ Mongoid::Haystack.index(A) }
61
+
62
+ assert{
63
+ results = Mongoid::Haystack.search('dog').map(&:model)
64
+ results.include?(a) and results.include?(b) and !results.include?(c)
65
+ }
66
+ end
67
+
68
+ ##
69
+ #
70
+ testing 'that counts are kept regarding each seen token' do
71
+ a = A.create!(:content => 'dog')
72
+ b = A.create!(:content => 'dogs')
73
+ c = A.create!(:content => 'cat')
74
+
75
+ assert{ Mongoid::Haystack.index(A) }
76
+
77
+ assert{ Mongoid::Haystack::Token.count == 2 }
78
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
79
+ assert{ Mongoid::Haystack::Count[:tokens].value == 3 }
80
+ end
81
+
82
+ testing 'that removing a model from the index decrements counts appropriately' do
83
+ #
84
+ a = A.create!(:content => 'dog')
85
+ b = A.create!(:content => 'cat')
86
+ c = A.create!(:content => 'cats dogs')
87
+
88
+ assert{ Mongoid::Haystack.index(A) }
89
+
90
+ #
91
+ assert{ Mongoid::Haystack.search('cat').first }
92
+
93
+ assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 2 }
94
+ assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 2 }
95
+ assert{ Mongoid::Haystack::Count[:tokens].value == 4 }
96
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
97
+ assert{ Mongoid::Haystack.unindex(c) }
98
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
99
+ assert{ Mongoid::Haystack::Count[:tokens].value == 2 }
100
+ assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 1 }
101
+ assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 1 }
102
+
103
+ assert{ Mongoid::Haystack::Count[:tokens].value == 2 }
104
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
105
+ assert{ Mongoid::Haystack.unindex(b) }
106
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
107
+ assert{ Mongoid::Haystack::Count[:tokens].value == 1 }
108
+ assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 0 }
109
+ assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 1 }
110
+
111
+ assert{ Mongoid::Haystack::Count[:tokens].value == 1 }
112
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
113
+ assert{ Mongoid::Haystack.unindex(a) }
114
+ assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
115
+ assert{ Mongoid::Haystack::Count[:tokens].value == 0 }
116
+ assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 0 }
117
+ assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 0 }
118
+ end
119
+ end
data/test/testing.rb ADDED
@@ -0,0 +1,196 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test/unit'
3
+
4
+ testdir = File.expand_path(File.dirname(__FILE__))
5
+ rootdir = File.dirname(testdir)
6
+ libdir = File.join(rootdir, 'lib')
7
+
8
+ STDOUT.sync = true
9
+
10
+ $:.unshift(testdir) unless $:.include?(testdir)
11
+ $:.unshift(libdir) unless $:.include?(libdir)
12
+ $:.unshift(rootdir) unless $:.include?(rootdir)
13
+
14
+ class Testing
15
+ class Slug < ::String
16
+ def Slug.for(*args)
17
+ string = args.flatten.compact.join('-')
18
+ words = string.to_s.scan(%r/\w+/)
19
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
20
+ words.delete_if{|word| word.nil? or word.strip.empty?}
21
+ new(words.join('-').downcase)
22
+ end
23
+ end
24
+
25
+ class Context
26
+ attr_accessor :name
27
+
28
+ def initialize(name, *args)
29
+ @name = name
30
+ end
31
+
32
+ def to_s
33
+ Slug.for(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ def Testing(*args, &block)
39
+ Class.new(::Test::Unit::TestCase) do
40
+
41
+ ## class methods
42
+ #
43
+ class << self
44
+ def contexts
45
+ @contexts ||= []
46
+ end
47
+
48
+ def context(*args, &block)
49
+ return contexts.last if(args.empty? and block.nil?)
50
+
51
+ context = Testing::Context.new(*args)
52
+ contexts.push(context)
53
+
54
+ begin
55
+ block.call(context)
56
+ ensure
57
+ contexts.pop
58
+ end
59
+ end
60
+
61
+ def slug_for(*args)
62
+ string = [context, args].flatten.compact.join('-')
63
+ words = string.to_s.scan(%r/\w+/)
64
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
65
+ words.delete_if{|word| word.nil? or word.strip.empty?}
66
+ words.join('-').downcase.sub(/_$/, '')
67
+ end
68
+
69
+ def name() const_get(:Name) end
70
+
71
+ def testno()
72
+ '%05d' % (@testno ||= 0)
73
+ ensure
74
+ @testno += 1
75
+ end
76
+
77
+ def testing(*args, &block)
78
+ method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
79
+ define_method(method, &block)
80
+ end
81
+
82
+ def test(*args, &block)
83
+ testing(*args, &block)
84
+ end
85
+
86
+ def setup(&block)
87
+ define_method(:setup, &block) if block
88
+ end
89
+
90
+ def teardown(&block)
91
+ define_method(:teardown, &block) if block
92
+ end
93
+
94
+ def prepare(&block)
95
+ @prepare ||= []
96
+ @prepare.push(block) if block
97
+ @prepare
98
+ end
99
+
100
+ def cleanup(&block)
101
+ @cleanup ||= []
102
+ @cleanup.push(block) if block
103
+ @cleanup
104
+ end
105
+ end
106
+
107
+ ## configure the subclass!
108
+ #
109
+ const_set(:Testno, '0')
110
+ slug = slug_for(*args).gsub(%r/-/,'_')
111
+ name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
112
+ name = name.upcase!
113
+ const_set(:Name, name)
114
+ const_set(:Missing, Object.new.freeze)
115
+
116
+ ## instance methods
117
+ #
118
+ alias_method('__assert__', 'assert')
119
+
120
+ def assert(*args, &block)
121
+ if args.size == 1 and args.first.is_a?(Hash)
122
+ options = args.first
123
+ expected = getopt(:expected, options){ missing }
124
+ actual = getopt(:actual, options){ missing }
125
+ if expected == missing and actual == missing
126
+ actual, expected, *ignored = options.to_a.flatten
127
+ end
128
+ expected = expected.call() if expected.respond_to?(:call)
129
+ actual = actual.call() if actual.respond_to?(:call)
130
+ assert_equal(expected, actual)
131
+ end
132
+
133
+ if block
134
+ label = "assert(#{ args.join(' ') })"
135
+ result = nil
136
+ assert_nothing_raised{ result = block.call }
137
+ __assert__(result, label)
138
+ result
139
+ else
140
+ result = args.shift
141
+ label = "assert(#{ args.join(' ') })"
142
+ __assert__(result, label)
143
+ result
144
+ end
145
+ end
146
+
147
+ def missing
148
+ self.class.const_get(:Missing)
149
+ end
150
+
151
+ def getopt(opt, hash, options = nil, &block)
152
+ [opt.to_s, opt.to_s.to_sym].each do |key|
153
+ return hash[key] if hash.has_key?(key)
154
+ end
155
+ default =
156
+ if block
157
+ block.call
158
+ else
159
+ options.is_a?(Hash) ? options[:default] : nil
160
+ end
161
+ return default
162
+ end
163
+
164
+ def subclass_of exception
165
+ class << exception
166
+ def ==(other) super or self > other end
167
+ end
168
+ exception
169
+ end
170
+
171
+ ##
172
+ #
173
+ module_eval(&block)
174
+
175
+ self.setup()
176
+ self.prepare.each{|b| b.call()}
177
+
178
+ at_exit{
179
+ self.teardown()
180
+ self.cleanup.each{|b| b.call()}
181
+ }
182
+
183
+ self
184
+ end
185
+ end
186
+
187
+
188
+ if $0 == __FILE__
189
+
190
+ Testing 'Testing' do
191
+ testing('foo'){ assert true }
192
+ test{ assert true }
193
+ p instance_methods.grep(/test/)
194
+ end
195
+
196
+ end