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 ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+
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.all.each { |artwork| artwork.update_ngram_index }
252
+ Artwork.update_ngram_index
253
+ Artwork.find(id).update_ngram_index
249
254
 
250
- You can also remove instances in bulk from the index with the `remove_from_ngram_index`
255
+ You can remove all or individual instances from the index with the `remove_from_ngram_index`
251
256
  method:
252
257
 
253
- Artwork.all.each { |artwork| artwork.remove_from_ngram_index }
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.3.6
1
+ 0.4.0
@@ -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
- coll = collection.db.collection(index_name)
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
- # options hash should only contain filters after this point
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
- query = {'ngram' => {'$in' => ngrams.keys}}
60
- query.update(Hash[options.map { |key,value| [ 'filter_values.%s' % key, { '$all' => [ value ].flatten } ] }])
61
- map = <<-EOS
62
- function() {
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
- if collection.db.connection.server_version >= '1.7.4'
79
- mr_options[:out] = {:inline => 1}
80
- results = coll.map_reduce(map, reduce, mr_options)['results'].sort_by{ |x| -x['value']['score'] }
81
- max_results = results.count if max_results.nil?
82
- instantiate_mapreduce_results(results.first(max_results), rc_options)
83
- else
84
- result_collection = coll.map_reduce(map, reduce, mr_options)['result']
85
- results = collection.db.collection(result_collection).find.sort(['value.score',-1])
86
- results = results.limit(max_results) if !max_results.nil?
87
- models = instantiate_mapreduce_results(results, rc_options)
88
- collection.db.collection(result_collection).drop
89
- models
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
- Object::const_get(result['value']['class']).find(:first, :conditions => {:id => result['_id']})
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['value']['score'] ] }.find_all { |result| ! result[0].nil? }
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) }.find_all { |result| ! result.nil? }
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] }.find_all{ |ch| !ch.nil? }.join('')
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.find_all{ |x| !x.nil? }]
229
+ end.compact]
166
230
  end
167
231
  # insert new ngrams in external index
168
232
  ngrams.each_pair do |ngram, score|
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongoid_fulltext}
8
- s.version = "0.3.6"
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-05-27}
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.3.7}
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
@@ -0,0 +1,9 @@
1
+ module Gallery
2
+ class BasicArtwork
3
+ include Mongoid::Document
4
+ include Mongoid::FullTextSearch
5
+
6
+ field :title
7
+ fulltext_search_in :title
8
+ end
9
+ 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
- PartitionedArtist.fulltext_search('foobar').should == [ artist_2, artist_1, artist_0 ]
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" ]).should == [ artist_2, artist_1 ]
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/*.rb"].each { |f| require f }
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.3.6
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-05-27 00:00:00.000000000 -04:00
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: &82366030 !ruby/object:Gem::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: *82366030
25
+ version_requirements: *70446910
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: database_cleaner
28
- requirement: &82365790 !ruby/object:Gem::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: *82365790
36
+ version_requirements: *70444760
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: rspec
39
- requirement: &82365550 !ruby/object:Gem::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: *82365550
47
+ version_requirements: *70397170
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: jeweler
50
- requirement: &82365310 !ruby/object:Gem::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: *82365310
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