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