mongoid_fulltext 0.3.6 → 0.4.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.
- data/.rspec +2 -0
- data/README.md +13 -4
- data/VERSION +1 -1
- data/lib/mongoid_fulltext.rb +108 -44
- data/mongoid_fulltext.gemspec +8 -4
- data/spec/models/filtered_other.rb +11 -0
- data/spec/models/gallery/basic_artwork.rb +9 -0
- data/spec/mongoid/fulltext_spec.rb +91 -2
- data/spec/spec_helper.rb +2 -1
- metadata +18 -10
data/.rspec
ADDED
data/README.md
CHANGED
@@ -207,6 +207,10 @@ Additional indexing/query options can be used as parameters to `fulltext_search_
|
|
207
207
|
* `index_full_words`: index full words, which improves exact matches, default is `true`
|
208
208
|
* `apply_prefix_scoring_to_all_words`: score n-grams at beginning of words higher, default is `true`
|
209
209
|
* `max_ngrams_to_search`: maximum number of ngrams to query at any given time, default is `6`
|
210
|
+
* `max_candidate_set_size`: maximum number of candidate ngrams to examine for a given query.
|
211
|
+
Defaults to 1000. If you're seeing poor results, you can try increasing this value to consider
|
212
|
+
more ngrams per query (changing this parameter does not require a re-index.) The amount of time
|
213
|
+
a search takes is directly proportional to this parameter's value.
|
210
214
|
|
211
215
|
Array filters
|
212
216
|
-------------
|
@@ -243,14 +247,19 @@ Building the index
|
|
243
247
|
|
244
248
|
The fulltext index is built and maintained incrementally by hooking into `before_save` and
|
245
249
|
`before_destroy` callbacks on each model that's being indexed. If you want to build an index
|
246
|
-
on existing models, you can call the `update_ngram_index` method on each instance:
|
250
|
+
on existing models, you can call the `update_ngram_index` method on the class or each instance:
|
247
251
|
|
248
|
-
Artwork.
|
252
|
+
Artwork.update_ngram_index
|
253
|
+
Artwork.find(id).update_ngram_index
|
249
254
|
|
250
|
-
You can
|
255
|
+
You can remove all or individual instances from the index with the `remove_from_ngram_index`
|
251
256
|
method:
|
252
257
|
|
253
|
-
Artwork.
|
258
|
+
Artwork.remove_from_ngram_index
|
259
|
+
Artwork.find(id).remove_from_ngram_index
|
260
|
+
|
261
|
+
The methods on the model level perform bulk removal operations and are therefore faster that
|
262
|
+
updating or removing records individually.
|
254
263
|
|
255
264
|
Running the specs
|
256
265
|
-----------------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/mongoid_fulltext.rb
CHANGED
@@ -8,7 +8,7 @@ module Mongoid::FullTextSearch
|
|
8
8
|
class UnspecifiedIndexError < StandardError; end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
|
11
|
+
|
12
12
|
def fulltext_search_in(*args)
|
13
13
|
self.mongoid_fulltext_config = {} if self.mongoid_fulltext_config.nil?
|
14
14
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
@@ -24,7 +24,8 @@ module Mongoid::FullTextSearch
|
|
24
24
|
:ngram_width => 3,
|
25
25
|
:max_ngrams_to_search => 6,
|
26
26
|
:apply_prefix_scoring_to_all_words => true,
|
27
|
-
:index_full_words => true
|
27
|
+
:index_full_words => true,
|
28
|
+
:max_candidate_set_size => 1000
|
28
29
|
}
|
29
30
|
|
30
31
|
config.update(options)
|
@@ -35,14 +36,41 @@ module Mongoid::FullTextSearch
|
|
35
36
|
config[:word_separators] = Hash[config[:word_separators].split('').map{ |ch| [ch,ch] }]
|
36
37
|
self.mongoid_fulltext_config[index_name] = config
|
37
38
|
|
38
|
-
|
39
|
-
coll.ensure_index([['ngram', Mongo::ASCENDING]])
|
40
|
-
coll.ensure_index([['document_id', Mongo::ASCENDING]])
|
39
|
+
ensure_indexes(index_name, config)
|
41
40
|
|
42
41
|
before_save :update_ngram_index
|
43
42
|
before_destroy :remove_from_ngram_index
|
44
43
|
end
|
45
44
|
|
45
|
+
def ensure_indexes(index_name, config)
|
46
|
+
db = collection.db
|
47
|
+
coll = db.collection(index_name)
|
48
|
+
|
49
|
+
filter_indexes = (config[:filters] || []).map do |key,value|
|
50
|
+
["filter_values.#{key}", Mongo::ASCENDING]
|
51
|
+
end
|
52
|
+
index_definition = [['ngram', Mongo::ASCENDING], ['score', Mongo::DESCENDING]].concat(filter_indexes)
|
53
|
+
|
54
|
+
# Since the definition of the index could have changed, we'll clean up by
|
55
|
+
# removing any indexes that aren't on the exact
|
56
|
+
correct_keys = index_definition.map{ |field_def| field_def[0] }
|
57
|
+
all_filter_keys = filter_indexes.map{ |field_def| field_def[0] }
|
58
|
+
coll.index_information.each do |name, definition|
|
59
|
+
keys = definition['key'].keys
|
60
|
+
next if !keys.member?('ngram')
|
61
|
+
all_filter_keys |= keys.find_all{ |key| key.starts_with?('filter_values.') }
|
62
|
+
coll.drop_index(name) if keys & correct_keys != correct_keys
|
63
|
+
end
|
64
|
+
|
65
|
+
if all_filter_keys.length > filter_indexes.length
|
66
|
+
filter_indexes = all_filter_keys.map { |key| [key, Mongo::ASCENDING] }
|
67
|
+
index_definition = [['ngram', Mongo::ASCENDING], ['score', Mongo::DESCENDING]].concat(filter_indexes)
|
68
|
+
end
|
69
|
+
|
70
|
+
coll.ensure_index(index_definition, name: 'fts_index')
|
71
|
+
coll.ensure_index([['document_id', Mongo::ASCENDING]]) # to make removes fast
|
72
|
+
end
|
73
|
+
|
46
74
|
def fulltext_search(query_string, options={})
|
47
75
|
max_results = options.has_key?(:max_results) ? options.delete(:max_results) : 10
|
48
76
|
return_scores = options.has_key?(:return_scores) ? options.delete(:return_scores) : false
|
@@ -51,61 +79,84 @@ module Mongoid::FullTextSearch
|
|
51
79
|
raise UnspecifiedIndexError, error_message % self.name, caller
|
52
80
|
end
|
53
81
|
index_name = options.has_key?(:index) ? options.delete(:index) : self.mongoid_fulltext_config.keys.first
|
54
|
-
|
55
|
-
#
|
82
|
+
|
83
|
+
# Options hash should only contain filters after this point
|
84
|
+
|
56
85
|
ngrams = all_ngrams(query_string, self.mongoid_fulltext_config[index_name])
|
57
86
|
return [] if ngrams.empty?
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
emit(this['document_id'], {'class': this['class'], 'score': this['score']*ngrams[this['ngram']] })
|
64
|
-
}
|
65
|
-
EOS
|
66
|
-
reduce = <<-EOS
|
67
|
-
function(key, values) {
|
68
|
-
score = 0.0
|
69
|
-
for (i in values) {
|
70
|
-
score += values[i]['score']
|
71
|
-
}
|
72
|
-
return({'class': values[0]['class'], 'score': score})
|
73
|
-
}
|
74
|
-
EOS
|
75
|
-
mr_options = {:scope => {:ngrams => ngrams }, :query => query, :raw => true}
|
76
|
-
rc_options = { :return_scores => return_scores }
|
87
|
+
|
88
|
+
# For each ngram, construct the query we'll use to pull index documents and
|
89
|
+
# get a count of the number of index documents containing that n-gram
|
90
|
+
ordering = [['score', Mongo::DESCENDING]]
|
91
|
+
limit = self.mongoid_fulltext_config[index_name][:max_candidate_set_size]
|
77
92
|
coll = collection.db.collection(index_name)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
93
|
+
cursors = ngrams.map do |ngram|
|
94
|
+
query = {'ngram' => ngram[0]}
|
95
|
+
query.update(Hash[options.map { |key,value| [ 'filter_values.%s' % key, { '$all' => [ value ].flatten } ] }])
|
96
|
+
count = coll.find(query).count
|
97
|
+
{:ngram => ngram, :count => count, :query => query}
|
98
|
+
end.sort_by!{ |record| record[:count] }
|
99
|
+
|
100
|
+
# Using the queries we just constructed and the n-gram frequency counts we
|
101
|
+
# just computed, pull in about *:max_candidate_set_size* candidates by
|
102
|
+
# considering the n-grams in order of increasing frequency. When we've
|
103
|
+
# spent all *:max_candidate_set_size* candidates, pull the top-scoring
|
104
|
+
# *max_results* candidates for each remaining n-gram.
|
105
|
+
results_so_far = 0
|
106
|
+
candidates_list = cursors.map do |doc|
|
107
|
+
next if doc[:count] == 0
|
108
|
+
query_options = {}
|
109
|
+
if results_so_far >= limit
|
110
|
+
query_options = {:sort => ordering, :limit => max_results}
|
111
|
+
elsif doc[:count] > limit - results_so_far
|
112
|
+
query_options = {:sort => ordering, :limit => limit - results_so_far}
|
113
|
+
end
|
114
|
+
results_so_far += doc[:count]
|
115
|
+
ngram_score = ngrams[doc[:ngram][0]]
|
116
|
+
Hash[coll.find(doc[:query], query_options).map do |candidate|
|
117
|
+
[candidate['document_id'],
|
118
|
+
{clazz: candidate['class'], score: candidate['score'] * ngram_score}]
|
119
|
+
end]
|
120
|
+
end.compact
|
121
|
+
|
122
|
+
# Finally, score all candidates by matching them up with other candidates that are
|
123
|
+
# associated with the same document. This is similar to how you might process a
|
124
|
+
# boolean AND query, except that with an AND query, you'd stop after considering
|
125
|
+
# the first candidate list and matching its candidates up with candidates from other
|
126
|
+
# lists, whereas here we want the search to be a little fuzzier so we'll run through
|
127
|
+
# all candidate lists, removing candidates as we match them up.
|
128
|
+
all_scores = []
|
129
|
+
while !candidates_list.empty?
|
130
|
+
candidates = candidates_list.pop
|
131
|
+
scores = candidates.map do |candidate_id, data|
|
132
|
+
{:id => candidate_id,
|
133
|
+
:clazz => data[:clazz],
|
134
|
+
:score => data[:score] + candidates_list.map{ |others| (others.delete(candidate_id) || {score: 0})[:score] }.sum
|
135
|
+
}
|
136
|
+
end
|
137
|
+
all_scores.concat(scores)
|
90
138
|
end
|
139
|
+
all_scores.sort_by!{ |document| -document[:score] }
|
140
|
+
|
141
|
+
instantiate_mapreduce_results(all_scores[0..max_results-1], { :return_scores => return_scores })
|
91
142
|
end
|
92
143
|
|
93
144
|
def instantiate_mapreduce_result(result)
|
94
|
-
|
145
|
+
result[:clazz].constantize.find(:first, :conditions => {'_id' => result[:id]})
|
95
146
|
end
|
96
147
|
|
97
148
|
def instantiate_mapreduce_results(results, options)
|
98
149
|
if (options[:return_scores])
|
99
|
-
results.map { |result| [ instantiate_mapreduce_result(result), result[
|
150
|
+
results.map { |result| [ instantiate_mapreduce_result(result), result[:score] ] }.find_all { |result| ! result[0].nil? }
|
100
151
|
else
|
101
|
-
results.map { |result| instantiate_mapreduce_result(result) }.
|
152
|
+
results.map { |result| instantiate_mapreduce_result(result) }.compact
|
102
153
|
end
|
103
154
|
end
|
104
155
|
|
105
156
|
# returns an [ngram, score] [ngram, position] pair
|
106
157
|
def all_ngrams(str, config, bound_number_returned = true)
|
107
158
|
return {} if str.nil? or str.length < config[:ngram_width]
|
108
|
-
filtered_str = str.downcase.split('').map{ |ch| config[:alphabet][ch] }.
|
159
|
+
filtered_str = str.downcase.split('').map{ |ch| config[:alphabet][ch] }.compact.join('')
|
109
160
|
|
110
161
|
if bound_number_returned
|
111
162
|
step_size = [((filtered_str.length - config[:ngram_width]).to_f / config[:max_ngrams_to_search]).ceil, 1].max
|
@@ -141,7 +192,20 @@ module Mongoid::FullTextSearch
|
|
141
192
|
|
142
193
|
ngram_hash
|
143
194
|
end
|
144
|
-
|
195
|
+
|
196
|
+
def remove_from_ngram_index
|
197
|
+
self.mongoid_fulltext_config.each_pair do |index_name, fulltext_config|
|
198
|
+
coll = collection.db.collection(index_name)
|
199
|
+
coll.remove({'class' => self.name})
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def update_ngram_index
|
204
|
+
self.all.each do |model|
|
205
|
+
model.update_ngram_index
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
145
209
|
end
|
146
210
|
|
147
211
|
def update_ngram_index
|
@@ -162,7 +226,7 @@ module Mongoid::FullTextSearch
|
|
162
226
|
rescue
|
163
227
|
# Suppress any exceptions caused by filters
|
164
228
|
end
|
165
|
-
end.
|
229
|
+
end.compact]
|
166
230
|
end
|
167
231
|
# insert new ngrams in external index
|
168
232
|
ngrams.each_pair do |ngram, score|
|
data/mongoid_fulltext.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mongoid_fulltext}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Aaron Windsor"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-07-19}
|
13
13
|
s.description = %q{Full-text search for the Mongoid ORM, using n-grams extracted from text}
|
14
14
|
s.email = %q{aaron.windsor@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
+
".rspec",
|
21
22
|
"Gemfile",
|
22
23
|
"LICENSE",
|
23
24
|
"README.md",
|
@@ -32,6 +33,8 @@ Gem::Specification.new do |s|
|
|
32
33
|
"spec/models/external_artwork_no_fields_supplied.rb",
|
33
34
|
"spec/models/filtered_artist.rb",
|
34
35
|
"spec/models/filtered_artwork.rb",
|
36
|
+
"spec/models/filtered_other.rb",
|
37
|
+
"spec/models/gallery/basic_artwork.rb",
|
35
38
|
"spec/models/multi_external_artwork.rb",
|
36
39
|
"spec/models/multi_field_artist.rb",
|
37
40
|
"spec/models/multi_field_artwork.rb",
|
@@ -42,7 +45,7 @@ Gem::Specification.new do |s|
|
|
42
45
|
s.homepage = %q{http://github.com/aaw/mongoid_fulltext}
|
43
46
|
s.licenses = ["MIT"]
|
44
47
|
s.require_paths = ["lib"]
|
45
|
-
s.rubygems_version = %q{1.
|
48
|
+
s.rubygems_version = %q{1.6.2}
|
46
49
|
s.summary = %q{Full-text search for the Mongoid ORM}
|
47
50
|
s.test_files = [
|
48
51
|
"spec/models/advanced_artwork.rb",
|
@@ -52,6 +55,8 @@ Gem::Specification.new do |s|
|
|
52
55
|
"spec/models/external_artwork_no_fields_supplied.rb",
|
53
56
|
"spec/models/filtered_artist.rb",
|
54
57
|
"spec/models/filtered_artwork.rb",
|
58
|
+
"spec/models/filtered_other.rb",
|
59
|
+
"spec/models/gallery/basic_artwork.rb",
|
55
60
|
"spec/models/multi_external_artwork.rb",
|
56
61
|
"spec/models/multi_field_artist.rb",
|
57
62
|
"spec/models/multi_field_artwork.rb",
|
@@ -61,7 +66,6 @@ Gem::Specification.new do |s|
|
|
61
66
|
]
|
62
67
|
|
63
68
|
if s.respond_to? :specification_version then
|
64
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
65
69
|
s.specification_version = 3
|
66
70
|
|
67
71
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This is some other model that lives in the same index with FilteredArtist and FilteredArtwork,
|
2
|
+
# to make sure different filters can co-exist in the same index and are indexed properly.
|
3
|
+
class FilteredOther
|
4
|
+
include Mongoid::Document
|
5
|
+
include Mongoid::FullTextSearch
|
6
|
+
field :name
|
7
|
+
fulltext_search_in :name, :index_name => 'mongoid_fulltext.artworks_and_artists',
|
8
|
+
:filters => { :is_fuzzy => lambda { |x| true },
|
9
|
+
:is_awesome => lambda { |x| false }
|
10
|
+
}
|
11
|
+
end
|
@@ -62,6 +62,23 @@ module Mongoid
|
|
62
62
|
end
|
63
63
|
|
64
64
|
end
|
65
|
+
|
66
|
+
context "with default settings" do
|
67
|
+
|
68
|
+
let!(:flower_myth) { Gallery::BasicArtwork.create(:title => 'Flower Myth') }
|
69
|
+
let!(:flowers) { Gallery::BasicArtwork.create(:title => 'Flowers') }
|
70
|
+
let!(:lowered) { Gallery::BasicArtwork.create(:title => 'Lowered') }
|
71
|
+
let!(:cookies) { Gallery::BasicArtwork.create(:title => 'Cookies') }
|
72
|
+
let!(:empty) { Gallery::BasicArtwork.create(:title => '') }
|
73
|
+
|
74
|
+
it "returns exact matches for model within a module" do
|
75
|
+
Gallery::BasicArtwork.fulltext_search('Flower Myth', :max_results => 1).first.should == flower_myth
|
76
|
+
Gallery::BasicArtwork.fulltext_search('Flowers', :max_results => 1).first.should == flowers
|
77
|
+
Gallery::BasicArtwork.fulltext_search('Cookies', :max_results => 1).first.should == cookies
|
78
|
+
Gallery::BasicArtwork.fulltext_search('Lowered', :max_results => 1).first.should == lowered
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
65
82
|
|
66
83
|
context "with default settings" do
|
67
84
|
|
@@ -310,6 +327,26 @@ module Mongoid
|
|
310
327
|
end
|
311
328
|
|
312
329
|
end
|
330
|
+
|
331
|
+
context "with different filters applied to multiple models" do
|
332
|
+
let!(:foo_artwork) { FilteredArtwork.create(:title => 'foo') }
|
333
|
+
let!(:bar_artist) { FilteredArtist.create(:full_name => 'bar') }
|
334
|
+
let!(:baz_other) { FilteredOther.create(:name => 'baz') }
|
335
|
+
|
336
|
+
# These three models are all indexed by the same mongoid_fulltext index, but have different filters
|
337
|
+
# applied. The index created on the mongoid_fulltext collection should include the ngram and score
|
338
|
+
# fields as well as the union of all the filter fields to allow for efficient lookups.
|
339
|
+
|
340
|
+
it "creates a proper index for searching efficiently" do
|
341
|
+
index_collection = FilteredArtwork.collection.db.collection('mongoid_fulltext.artworks_and_artists')
|
342
|
+
ngram_indexes = index_collection.index_information.find_all{ |name, definition| definition['key'].has_key?('ngram') }
|
343
|
+
ngram_indexes.length.should == 1
|
344
|
+
keys = ngram_indexes.first[1]['key'].keys
|
345
|
+
expected_keys = ['ngram','score', 'filter_values.is_fuzzy', 'filter_values.is_awesome',
|
346
|
+
'filter_values.is_foobar', 'filter_values.is_artwork', 'filter_values.is_artist'].sort
|
347
|
+
keys.sort.should == expected_keys
|
348
|
+
end
|
349
|
+
end
|
313
350
|
|
314
351
|
context "with partitions applied to a model" do
|
315
352
|
|
@@ -318,9 +355,11 @@ module Mongoid
|
|
318
355
|
let!(:artist_0) { PartitionedArtist.create(:full_name => 'foobar', :exhibitions => [ ]) }
|
319
356
|
|
320
357
|
it "allows partitioned searches" do
|
321
|
-
|
358
|
+
artists_by_exhibition_length = [ artist_0, artist_1, artist_2 ].sort_by{ |x| x.exhibitions.length }
|
359
|
+
PartitionedArtist.fulltext_search('foobar').sort_by{ |x| x.exhibitions.length }.should == artists_by_exhibition_length
|
322
360
|
PartitionedArtist.fulltext_search('foobar', :exhibitions => [ "Armory NY" ]).should == [ artist_2 ]
|
323
|
-
PartitionedArtist.fulltext_search('foobar', :exhibitions => [ "Art Basel 2011" ]).
|
361
|
+
art_basel_only = PartitionedArtist.fulltext_search('foobar', :exhibitions => [ "Art Basel 2011" ]).sort_by{ |x| x.exhibitions.length }
|
362
|
+
art_basel_only.should == [ artist_1, artist_2 ].sort_by{ |x| x.exhibitions.length }
|
324
363
|
PartitionedArtist.fulltext_search('foobar', :exhibitions => [ "Art Basel 2011", "Armory NY" ]).should == [ artist_2 ]
|
325
364
|
end
|
326
365
|
|
@@ -343,6 +382,56 @@ module Mongoid
|
|
343
382
|
first_result[1].is_a?(Float).should be_true
|
344
383
|
end
|
345
384
|
end
|
385
|
+
|
386
|
+
context "remove_from_ngram_index" do
|
387
|
+
let!(:flowers1) { BasicArtwork.create(:title => 'Flowers 1') }
|
388
|
+
let!(:flowers2) { BasicArtwork.create(:title => 'Flowers 1') }
|
389
|
+
|
390
|
+
it "removes all records from the index" do
|
391
|
+
BasicArtwork.remove_from_ngram_index
|
392
|
+
BasicArtwork.fulltext_search('flower').length.should == 0
|
393
|
+
end
|
394
|
+
|
395
|
+
it "removes a single record from the index" do
|
396
|
+
flowers1.remove_from_ngram_index
|
397
|
+
BasicArtwork.fulltext_search('flower').length.should == 1
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context "update_ngram_index" do
|
402
|
+
let!(:flowers1) { BasicArtwork.create(:title => 'Flowers 1') }
|
403
|
+
let!(:flowers2) { BasicArtwork.create(:title => 'Flowers 2') }
|
404
|
+
|
405
|
+
context "from scratch" do
|
406
|
+
|
407
|
+
before(:each) do
|
408
|
+
Mongoid.master["mongoid_fulltext.index_basicartwork_0"].remove
|
409
|
+
end
|
410
|
+
|
411
|
+
it "updates index on a single record" do
|
412
|
+
flowers1.update_ngram_index
|
413
|
+
BasicArtwork.fulltext_search('flower').length.should == 1
|
414
|
+
end
|
415
|
+
|
416
|
+
it "updates index on all records" do
|
417
|
+
BasicArtwork.update_ngram_index
|
418
|
+
BasicArtwork.fulltext_search('flower').length.should == 2
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
context "incremental" do
|
424
|
+
|
425
|
+
it "removes an existing record" do
|
426
|
+
coll = Mongoid.master["mongoid_fulltext.index_basicartwork_0"]
|
427
|
+
Mongoid.master.stub(:collection).with("mongoid_fulltext.index_basicartwork_0").and_return { coll }
|
428
|
+
coll.should_receive(:remove).once.with({'document_id' => flowers1._id})
|
429
|
+
flowers1.update_ngram_index
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|
346
435
|
|
347
436
|
end
|
348
437
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,10 +11,11 @@ Mongoid.configure do |config|
|
|
11
11
|
end
|
12
12
|
|
13
13
|
require File.expand_path("../../lib/mongoid_fulltext", __FILE__)
|
14
|
-
Dir["#{File.dirname(__FILE__)}/models
|
14
|
+
Dir["#{File.dirname(__FILE__)}/models/**/*.rb"].each { |f| require f }
|
15
15
|
|
16
16
|
Rspec.configure do |c|
|
17
17
|
c.before(:all) { DatabaseCleaner.strategy = :truncation }
|
18
18
|
c.before(:each) { DatabaseCleaner.clean }
|
19
|
+
c.after(:all) { Mongoid.master.command({'repairDatabase' => 1}) }
|
19
20
|
end
|
20
21
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid_fulltext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-07-19 00:00:00.000000000 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: mongoid
|
17
|
-
requirement: &
|
17
|
+
requirement: &70446910 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 2.0.0
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70446910
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: database_cleaner
|
28
|
-
requirement: &
|
28
|
+
requirement: &70444760 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 0.6.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70444760
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: rspec
|
39
|
-
requirement: &
|
39
|
+
requirement: &70397170 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 2.5.0
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70397170
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: jeweler
|
50
|
-
requirement: &
|
50
|
+
requirement: &70386800 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
version: 1.5.2
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70386800
|
59
59
|
description: Full-text search for the Mongoid ORM, using n-grams extracted from text
|
60
60
|
email: aaron.windsor@gmail.com
|
61
61
|
executables: []
|
@@ -65,6 +65,7 @@ extra_rdoc_files:
|
|
65
65
|
- README.md
|
66
66
|
files:
|
67
67
|
- .document
|
68
|
+
- .rspec
|
68
69
|
- Gemfile
|
69
70
|
- LICENSE
|
70
71
|
- README.md
|
@@ -79,6 +80,8 @@ files:
|
|
79
80
|
- spec/models/external_artwork_no_fields_supplied.rb
|
80
81
|
- spec/models/filtered_artist.rb
|
81
82
|
- spec/models/filtered_artwork.rb
|
83
|
+
- spec/models/filtered_other.rb
|
84
|
+
- spec/models/gallery/basic_artwork.rb
|
82
85
|
- spec/models/multi_external_artwork.rb
|
83
86
|
- spec/models/multi_field_artist.rb
|
84
87
|
- spec/models/multi_field_artwork.rb
|
@@ -99,6 +102,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
102
|
- - ! '>='
|
100
103
|
- !ruby/object:Gem::Version
|
101
104
|
version: '0'
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
hash: 858859311
|
102
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
109
|
none: false
|
104
110
|
requirements:
|
@@ -119,6 +125,8 @@ test_files:
|
|
119
125
|
- spec/models/external_artwork_no_fields_supplied.rb
|
120
126
|
- spec/models/filtered_artist.rb
|
121
127
|
- spec/models/filtered_artwork.rb
|
128
|
+
- spec/models/filtered_other.rb
|
129
|
+
- spec/models/gallery/basic_artwork.rb
|
122
130
|
- spec/models/multi_external_artwork.rb
|
123
131
|
- spec/models/multi_field_artist.rb
|
124
132
|
- spec/models/multi_field_artwork.rb
|