mongoid-haystack 1.0.0

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