Skip to content

Commit 44be8ee

Browse files
committed
Added support to have different indexes from which search
1 parent baaa432 commit 44be8ee

File tree

5 files changed

+80
-17
lines changed

5 files changed

+80
-17
lines changed

README.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ class Product
2626
include Mongoid::Search
2727
field :brand
2828
field :name
29+
field :unit
2930
field :info, type: Hash
3031

3132
has_many :tags
3233
belongs_to :category
3334

3435
search_in :brand, :name, tags: :name, category: :name, info: %i[summary description]
36+
search_in :unit, index: :_unit_keywords
3537
end
3638

3739
class Tag
@@ -52,14 +54,16 @@ end
5254
Now when you save a product, you get a `_keywords` field automatically:
5355

5456
```ruby
55-
p = Product.new brand: 'Apple', name: 'iPhone', info: { summary: 'Info-summary', description: 'Info-description' }
57+
p = Product.new brand: 'Apple', name: 'iPhone', unit: 'kilogram', info: { summary: 'Info-summary', description: 'Info-description' }
5658
p.tags << Tag.new(name: 'Amazing')
5759
p.tags << Tag.new(name: 'Awesome')
5860
p.tags << Tag.new(name: 'Superb')
5961
p.save
6062
# => true
6163
p._keywords
6264
# => ["amazing", "apple", "awesome", "iphone", "superb", "Info-summary", "Info-description"]
65+
p._unit_keywords
66+
# => ["kilogram"]
6367
```
6468

6569
Now you can run search, which will look in the `_keywords` field and return all matching results:
@@ -69,6 +73,13 @@ Product.full_text_search("apple iphone").size
6973
# => 1
7074
```
7175

76+
Of course, some models could have more than one index. For instance, two different searches with different fields, so you could even specify from which index should be searched:
77+
78+
```ruby
79+
Product.full_text_search("kilogram", index: :_unit_keywords).size
80+
# => 1
81+
```
82+
7283
Note that the search is case insensitive, and accept partial searching too:
7384

7485
```ruby
@@ -131,6 +142,20 @@ Product.full_text_search('amazing apple', relevant_search: true)
131142

132143
Please note that relevant_search will return an Array and not a Criteria object. The search method should always be called in the end of the method chain.
133144

145+
### index
146+
147+
Default is `_keywords`.
148+
149+
```ruby
150+
Product.full_text_search('amazing apple', index: :_keywords)
151+
# => [#<Product _id: 5016e7d16af54efe1c000001, _type: nil, brand: "Apple", name: "iPhone", unit: "l", attrs: nil, info: nil, category_id: nil, _keywords: ["amazing", "apple", "awesome", "iphone", "superb"], _unit_keywords: ["l"], relevance: 2.0>]
152+
153+
Product.full_text_search('kg', index: :_unit_keywords)
154+
# => [#<Product _id: 5016e7d16af54efe1c000001, _type: nil, brand: "Apple", name: "iPhone", unit: "kg", attrs: nil, info: nil, category_id: nil, _keywords: ["amazing", "apple", "awesome", "iphone", "superb"], _unit_keywords: ["kg"], relevance: 2.0>]
155+
```
156+
157+
index enables to have two or more different searches, with different or same fields. It should be noted that indexes are exclusive per each one.
158+
134159
## Initializer
135160

136161
Alternatively, you can create an initializer to setup those options:

lib/mongoid_search/mongoid_search.rb

+28-11
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def self.classes
1414
module ClassMethods #:nodoc:
1515
# Set a field or a number of fields as sources for search
1616
def search_in(*args)
17-
args, _options = args_and_options(args)
18-
self.search_fields = (search_fields || []).concat args
17+
args, options = args_and_options(args)
18+
set_search_fields(options[:index], args)
1919

20-
field :_keywords, type: Array
20+
field options[:index], type: Array
2121

22-
index({ _keywords: 1 }, background: true)
22+
index({ options[:index] => 1 }, background: true)
2323

2424
before_save :set_keywords
2525
end
@@ -49,13 +49,20 @@ def index_keywords!
4949

5050
private
5151

52+
def set_search_fields(index, fields)
53+
self.search_fields ||= {}
54+
55+
(self.search_fields[index] ||= []).concat fields
56+
end
57+
5258
def query(keywords, options)
5359
keywords_hash = keywords.map do |kw|
5460
if Mongoid::Search.regex_search
5561
escaped_kw = Regexp.escape(kw)
5662
kw = Mongoid::Search.regex.call(escaped_kw)
5763
end
58-
{ _keywords: kw }
64+
65+
{ options[:index] => kw }
5966
end
6067

6168
criteria.send("#{(options[:match])}_of", *keywords_hash)
@@ -65,6 +72,7 @@ def args_and_options(args)
6572
options = args.last.is_a?(Hash) &&
6673
%i[match
6774
allow_empty_search
75+
index
6876
relevant_search].include?(args.last.keys.first) ? args.pop : {}
6977

7078
[args, extract_options(options)]
@@ -74,7 +82,8 @@ def extract_options(options)
7482
{
7583
match: options[:match] || Mongoid::Search.match,
7684
allow_empty_search: options[:allow_empty_search] || Mongoid::Search.allow_empty_search,
77-
relevant_search: options[:relevant_search] || Mongoid::Search.relevant_search
85+
relevant_search: options[:relevant_search] || Mongoid::Search.relevant_search,
86+
index: options[:index] || :_keywords
7887
}
7988
end
8089

@@ -99,8 +108,8 @@ def results_with_relevance(query, options)
99108
function() {
100109
var entries = 0;
101110
for(i in keywords) {
102-
for(j in this._keywords) {
103-
if(this._keywords[j] == keywords[i]) {
111+
for(j in this.#{options[:index]}) {
112+
if(this.#{options[:index]}[j] == keywords[i]) {
104113
entries++;
105114
}
106115
}
@@ -122,11 +131,19 @@ def results_with_relevance(query, options)
122131
end
123132

124133
def index_keywords!
125-
update_attribute(:_keywords, set_keywords)
134+
search_fields.map do |index, fields|
135+
update_attribute(index, get_keywords(fields))
136+
end
126137
end
127138

128139
def set_keywords
129-
self._keywords = Mongoid::Search::Util.keywords(self, search_fields)
130-
.flatten.reject { |k| k.nil? || k.empty? }.uniq.sort
140+
search_fields.each do |index, fields|
141+
send("#{index}=", get_keywords(fields))
142+
end
143+
end
144+
145+
def get_keywords(fields)
146+
Mongoid::Search::Util.keywords(self, fields)
147+
.flatten.reject { |k| k.nil? || k.empty? }.uniq.sort
131148
end
132149
end

spec/models/product.rb

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ class Product
55

66
field :brand
77
field :name
8+
field :unit
9+
field :measures, type: Array
810
field :attrs, type: Array
911
field :info, type: Hash
1012

@@ -18,4 +20,5 @@ class Product
1820

1921
search_in :brand, :name, :outlet, :attrs, tags: :name, category: %i[name description],
2022
subproducts: %i[brand name], info: %i[summary description]
23+
search_in :unit, :measures, index: :_unit_keywords
2124
end

spec/models/variant.rb

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
autoload :Product, 'models/product.rb'
22
class Variant < Product
33
field :color
4+
field :size
45
search_in :color
6+
search_in :size, index: :_unit_keywords
57
end

spec/mongoid_search_spec.rb

+21-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Mongoid::Search.stem_proc = @default_proc
1919
@product = Product.create brand: 'Apple',
2020
name: 'iPhone',
21+
unit: 'mobile olé awesome',
2122
tags: (@tags = %w[Amazing Awesome Olé].map { |tag| Tag.new(name: tag) }),
2223
category: Category.new(name: 'Mobile', description: 'Reviews'),
2324
subproducts: [Subproduct.new(brand: 'Apple', name: 'Craddle')],
@@ -52,17 +53,20 @@
5253
Mongoid::Search.ignore_list = nil
5354
@product = Product.create brand: 'Эльбрус',
5455
name: 'Процессор',
56+
unit: 'kílográm Olé',
5557
tags: %w[Amazing Awesome Olé].map { |tag| Tag.new(name: tag) },
5658
category: Category.new(name: 'процессоры'),
5759
subproducts: []
5860
end
5961

6062
it 'should leave utf8 characters' do
6163
expect(@product._keywords).to eq %w[amazing awesome ole процессор процессоры эльбрус]
64+
expect(@product._unit_keywords).to eq %w[kilogram ole]
6265
end
6366

6467
it "should return results in search when case doesn't match" do
6568
expect(Product.full_text_search('ЭЛЬБРУС').size).to eq 1
69+
expect(Product.full_text_search('KILOGRAM', index: :_unit_keywords).size).to eq 1
6670
end
6771
end
6872

@@ -74,15 +78,18 @@
7478
end
7579

7680
it 'should validate keywords' do
77-
product = Product.create brand: 'Apple', name: 'iPhone'
81+
product = Product.create brand: 'Apple', name: 'iPhone', unit: 'box'
7882
expect(product._keywords).to eq(%w[apple iphone])
83+
expect(product._unit_keywords).to eq(%w[box])
7984
end
8085
end
8186

8287
it 'should set the _keywords field for array fields also' do
8388
@product.attrs = ['lightweight', 'plastic', :red]
89+
@product.measures = ['box', 'bunch', :bag]
8490
@product.save!
8591
expect(@product._keywords).to include 'lightweight', 'plastic', 'red'
92+
expect(@product._unit_keywords).to include 'box', 'bunch', 'bag'
8693
end
8794

8895
it 'should inherit _keywords field and build upon' do
@@ -91,39 +98,48 @@
9198
tags: %w[Amazing Awesome Olé].map { |tag| Tag.new(name: tag) },
9299
category: Category.new(name: 'Mobile'),
93100
subproducts: [Subproduct.new(brand: 'Apple', name: 'Craddle')],
94-
color: :white
101+
color: :white,
102+
size: :big
95103
expect(variant._keywords).to include 'white'
104+
expect(variant._unit_keywords).to include 'big'
96105
expect(Variant.full_text_search(name: 'Apple', color: :white)).to eq [variant]
106+
expect(Variant.full_text_search({ size: 'big' }, index: :_unit_keywords)).to eq [variant]
97107
end
98108

99109
it 'should expand the ligature to ease searching' do
100110
# ref: http://en.wikipedia.org/wiki/Typographic_ligature, only for french right now. Rules for other languages are not know
101111
variant1 = Variant.create tags: ['œuvre'].map { |tag| Tag.new(name: tag) }
102112
variant2 = Variant.create tags: ['æquo'].map { |tag| Tag.new(name: tag) }
113+
variant3 = Variant.create measures: ['ꜵquo'].map { |measure| measure }
103114

104115
expect(Variant.full_text_search('œuvre')).to eq [variant1]
105116
expect(Variant.full_text_search('oeuvre')).to eq [variant1]
106117
expect(Variant.full_text_search('æquo')).to eq [variant2]
107118
expect(Variant.full_text_search('aequo')).to eq [variant2]
119+
expect(Variant.full_text_search('aoquo', index: :_unit_keywords)).to eq [variant3]
120+
expect(Variant.full_text_search('ꜵquo', index: :_unit_keywords)).to eq [variant3]
108121
end
109122

110-
it 'should set the _keywords field with stemmed words if stem is enabled' do
123+
it 'should set the keywords fields with stemmed words if stem is enabled' do
111124
Mongoid::Search.stem_keywords = true
112125
@product.save!
113126
expect(@product._keywords.sort).to eq %w[amaz appl awesom craddl iphon mobil review ol info descript summari].sort
127+
expect(@product._unit_keywords.sort).to eq %w[mobil awesom ol].sort
114128
end
115129

116-
it 'should set the _keywords field with custom stemmed words if stem is enabled with a custom lambda' do
130+
it 'should set the keywords fields with custom stemmed words if stem is enabled with a custom lambda' do
117131
Mongoid::Search.stem_keywords = true
118132
Mongoid::Search.stem_proc = proc { |word| word.upcase }
119133
@product.save!
120134
expect(@product._keywords.sort).to eq %w[AMAZING APPLE AWESOME CRADDLE DESCRIPTION INFO IPHONE MOBILE OLE REVIEWS SUMMARY]
135+
expect(@product._unit_keywords.sort).to eq %w[AWESOME MOBILE OLE]
121136
end
122137

123138
it 'should ignore keywords in an ignore list' do
124139
Mongoid::Search.ignore_list = YAML.safe_load(File.open(File.dirname(__FILE__) + '/config/ignorelist.yml'))['ignorelist']
125140
@product.save!
126141
expect(@product._keywords.sort).to eq %w[apple craddle iphone mobile reviews ole info description summary].sort
142+
expect(@product._unit_keywords.sort).to eq %w[mobile ole].sort
127143
end
128144

129145
it 'should incorporate numbers as keywords' do
@@ -205,7 +221,7 @@
205221
end
206222

207223
it 'should have a method to index keywords' do
208-
expect(@product.index_keywords!).to eq true
224+
expect(@product.index_keywords!).to include(true)
209225
end
210226

211227
it 'should have a class method to index all documents keywords' do

0 commit comments

Comments
 (0)