mongoid_search 0.3.4 → 0.3.5
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.
- checksums.yaml +4 -4
- data/README.md +128 -115
- data/Rakefile +5 -2
- data/VERSION +1 -1
- data/lib/mongoid_search.rb +4 -8
- data/lib/mongoid_search/log.rb +2 -1
- data/lib/mongoid_search/mongoid_search.rb +23 -22
- data/lib/mongoid_search/tasks/mongoid_search.rake +1 -1
- data/lib/mongoid_search/util.rb +17 -18
- data/spec/models/product.rb +10 -6
- data/spec/models/subproduct.rb +1 -1
- data/spec/models/tag.rb +2 -2
- data/spec/models/variant.rb +1 -1
- data/spec/mongoid_search_spec.rb +158 -147
- data/spec/spec_helper.rb +4 -1
- data/spec/util_spec.rb +28 -29
- metadata +25 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 790663a1de866f407ace368cc503cd724cf4ffc6
|
4
|
+
data.tar.gz: f95fc5e91b0eb1b12eb95a60bd9c57688ae12bb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec1fd1b237c921939f66ca1b7a719f13a1275a19f42e86ef72f368ae7396b21c5fa6ab97e090a070ae656b45bccdaf6607e369a82754e9bfec828f2bca3a1c43
|
7
|
+
data.tar.gz: 3af9e42610b204de4a78e802bd3efaff38cabfa8a53a1f82b7af191cb9fe31e823e203f3087b606efe2d6fea38b0deb92b3751d2ab49674b410df06b1b40fb31
|
data/README.md
CHANGED
@@ -1,177 +1,190 @@
|
|
1
|
-
Mongoid Search
|
2
|
-
============
|
1
|
+
# Mongoid Search
|
3
2
|
|
4
|
-
Mongoid Search is a simple full text search implementation for Mongoid ORM. It performs well for small data sets. If your searchable model is big (i.e. 1.000.000+ records),
|
3
|
+
Mongoid Search is a simple full text search implementation for Mongoid ORM. It supports Mongoid 3, 4, 5 and 6 and performs well for small data sets. If your searchable model is big (i.e. 1.000.000+ records), [mongoid_fulltext](https://github.com/mongoid/mongoid_fulltext), ElasticSearch, Solr or Sphinx may suit you better.
|
5
4
|
|
6
|
-
|
7
|
-
--------
|
5
|
+
[](https://travis-ci.org/mongoid/mongoid_search)
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
gem 'mongoid_search'
|
7
|
+
## Installation
|
12
8
|
|
13
|
-
|
9
|
+
In your Gemfile:
|
14
10
|
|
15
|
-
|
11
|
+
```ruby
|
12
|
+
gem 'mongoid_search'
|
13
|
+
```
|
16
14
|
|
17
15
|
Then:
|
18
16
|
|
19
|
-
|
17
|
+
```
|
18
|
+
bundle install
|
19
|
+
```
|
20
20
|
|
21
|
-
Examples
|
22
|
-
--------
|
21
|
+
## Examples
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
```ruby
|
24
|
+
class Product
|
25
|
+
include Mongoid::Document
|
26
|
+
include Mongoid::Search
|
27
|
+
field :brand
|
28
|
+
field :name
|
29
|
+
field :info, type: Hash
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
has_many :tags
|
32
|
+
belongs_to :category
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
search_in :brand, :name, tags: :name, category: :name, info: %i[summary description]
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
class Tag
|
38
|
+
include Mongoid::Document
|
39
|
+
field :name
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
belongs_to :product
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
class Category
|
45
|
+
include Mongoid::Document
|
46
|
+
field :name
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
has_many :products
|
49
|
+
end
|
50
|
+
```
|
50
51
|
|
51
|
-
Now when you save a product, you get a _keywords field automatically:
|
52
|
+
Now when you save a product, you get a `_keywords` field automatically:
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
```ruby
|
55
|
+
p = Product.new brand: 'Apple', name: 'iPhone', info: { summary: 'Info-summary', description: 'Info-description' }
|
56
|
+
p.tags << Tag.new(name: 'Amazing')
|
57
|
+
p.tags << Tag.new(name: 'Awesome')
|
58
|
+
p.tags << Tag.new(name: 'Superb')
|
59
|
+
p.save
|
60
|
+
# => true
|
61
|
+
p._keywords
|
62
|
+
# => ["amazing", "apple", "awesome", "iphone", "superb", "Info-summary", "Info-description"]
|
63
|
+
```
|
61
64
|
|
62
|
-
Now you can run search, which will look in the _keywords field and return all matching results:
|
65
|
+
Now you can run search, which will look in the `_keywords` field and return all matching results:
|
63
66
|
|
64
|
-
|
65
|
-
|
67
|
+
```ruby
|
68
|
+
Product.full_text_search("apple iphone").size
|
69
|
+
# => 1
|
70
|
+
```
|
66
71
|
|
67
72
|
Note that the search is case insensitive, and accept partial searching too:
|
68
73
|
|
69
|
-
|
70
|
-
|
74
|
+
```ruby
|
75
|
+
Product.full_text_search('ipho').size
|
76
|
+
# => 1
|
77
|
+
```
|
71
78
|
|
72
|
-
Assuming you have a category with multiple products you can use the following
|
73
|
-
code to search for 'iphone' in products cheaper than $499
|
79
|
+
Assuming you have a category with multiple products you can use the following code to search for 'iphone' in products cheaper than $499.
|
74
80
|
|
75
|
-
|
81
|
+
```ruby
|
82
|
+
category.products.where(:price.lt => 499).full_text_search('iphone').asc(:price)
|
83
|
+
```
|
76
84
|
|
77
85
|
To index or reindex all existing records, run this rake task
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
-------
|
83
|
-
|
84
|
-
match:
|
85
|
-
|
86
|
-
_:any_ - match any occurrence
|
87
|
+
```
|
88
|
+
$ rake mongoid_search:index
|
89
|
+
```
|
87
90
|
|
88
|
-
|
91
|
+
## Options
|
89
92
|
|
90
|
-
|
93
|
+
### match
|
91
94
|
|
92
|
-
|
93
|
-
|
95
|
+
* `:any` - match any occurrence
|
96
|
+
* `:all` - match all occurrences
|
94
97
|
|
95
|
-
|
96
|
-
=> 0
|
98
|
+
Default is `:any`.
|
97
99
|
|
98
|
-
|
100
|
+
```ruby
|
101
|
+
Product.full_text_search('apple motorola', match: :any).size
|
102
|
+
# => 1
|
99
103
|
|
100
|
-
|
104
|
+
Product.full_text_search('apple motorola', match: :all).size
|
105
|
+
# => 0
|
106
|
+
```
|
101
107
|
|
102
|
-
|
108
|
+
### allow\_empty\_search
|
103
109
|
|
104
|
-
|
110
|
+
* `true` - will return `Model.all`
|
111
|
+
* `false` - will return `[]`
|
105
112
|
|
106
|
-
|
107
|
-
=> 1
|
113
|
+
Default is `false`.
|
108
114
|
|
109
|
-
|
115
|
+
```ruby
|
116
|
+
Product.full_text_search('', allow_empty_search: true).size
|
117
|
+
# => 1
|
118
|
+
```
|
110
119
|
|
111
|
-
|
120
|
+
### relevant_search
|
112
121
|
|
113
|
-
|
122
|
+
* `true` - adds relevance information to the results
|
123
|
+
* `false` - no relevance information
|
114
124
|
|
115
|
-
|
125
|
+
Default is `false`.
|
116
126
|
|
117
|
-
|
118
|
-
|
127
|
+
```ruby
|
128
|
+
Product.full_text_search('amazing apple', relevant_search: true)
|
129
|
+
# => [#<Product _id: 5016e7d16af54efe1c000001, _type: nil, brand: "Apple", name: "iPhone", attrs: nil, info: nil, category_id: nil, _keywords: ["amazing", "apple", "awesome", "iphone", "superb"], relevance: 2.0>]
|
130
|
+
```
|
119
131
|
|
120
|
-
|
132
|
+
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.
|
121
133
|
|
122
|
-
Initializer
|
123
|
-
-----------
|
134
|
+
## Initializer
|
124
135
|
|
125
136
|
Alternatively, you can create an initializer to setup those options:
|
126
137
|
|
127
|
-
|
128
|
-
|
129
|
-
|
138
|
+
```ruby
|
139
|
+
Mongoid::Search.setup do |config|
|
140
|
+
## Default matching type. Match :any or :all searched keywords
|
141
|
+
config.match = :any
|
130
142
|
|
131
|
-
|
132
|
-
|
143
|
+
## If true, an empty search will return all objects
|
144
|
+
config.allow_empty_search = false
|
133
145
|
|
134
|
-
|
135
|
-
|
146
|
+
## If true, will search with relevance information
|
147
|
+
config.relevant_search = false
|
136
148
|
|
137
|
-
|
138
|
-
|
149
|
+
## Stem keywords
|
150
|
+
config.stem_keywords = false
|
139
151
|
|
140
|
-
|
141
|
-
|
142
|
-
|
152
|
+
## Add a custom proc returning strings to replace the default stemmer
|
153
|
+
# For example using ruby-stemmer:
|
154
|
+
# config.stem_proc = Proc.new { |word| Lingua.stemmer(word, :language => 'nl') }
|
143
155
|
|
144
|
-
|
145
|
-
|
156
|
+
## Words to ignore
|
157
|
+
config.ignore_list = []
|
146
158
|
|
147
|
-
|
148
|
-
|
159
|
+
## An array of words
|
160
|
+
# config.ignore_list = %w{ a an to from as }
|
149
161
|
|
150
|
-
|
151
|
-
|
162
|
+
## Or from a file
|
163
|
+
# config.ignore_list = YAML.load(File.open(File.dirname(__FILE__) + '/config/ignorelist.yml'))["ignorelist"]
|
152
164
|
|
153
|
-
|
154
|
-
|
165
|
+
## Search using regex (slower)
|
166
|
+
config.regex_search = true
|
155
167
|
|
156
|
-
|
168
|
+
## Regex to search
|
157
169
|
|
158
|
-
|
159
|
-
|
170
|
+
## Match partial words on both sides (slower)
|
171
|
+
config.regex = Proc.new { |query| /#{query}/ }
|
160
172
|
|
161
|
-
|
162
|
-
|
163
|
-
|
173
|
+
## Match partial words on the beginning or in the end (slightly faster)
|
174
|
+
# config.regex = Proc.new { |query| /^#{query}/ }
|
175
|
+
# config.regex = Proc.new { |query| /#{query}$/ }
|
164
176
|
|
165
|
-
|
166
|
-
|
167
|
-
|
177
|
+
# Ligatures to be replaced
|
178
|
+
# http://en.wikipedia.org/wiki/Typographic_ligature
|
179
|
+
config.ligatures = { "œ"=>"oe", "æ"=>"ae" }
|
168
180
|
|
169
|
-
|
170
|
-
|
181
|
+
# Strip symbols regex to be replaced. These symbols will be replaced by space
|
182
|
+
config.strip_symbols = /[._:;'\"`,?|+={}()!@#%^&*<>~\$\-\\\/\[\]]/
|
171
183
|
|
172
|
-
|
173
|
-
|
184
|
+
# Strip accents regex to be replaced. These sybols will be removed after strip_symbols replacing
|
185
|
+
config.strip_accents = /[^\s\p{Alnum}]/
|
174
186
|
|
175
|
-
|
176
|
-
|
177
|
-
|
187
|
+
# Minimum word size. Words smaller than it won't be indexed
|
188
|
+
config.minimum_word_size = 2
|
189
|
+
end
|
190
|
+
```
|
data/Rakefile
CHANGED
@@ -3,8 +3,11 @@ require 'rake'
|
|
3
3
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
6
|
-
spec.rspec_opts = [
|
6
|
+
spec.rspec_opts = ['-c', '-f progress']
|
7
7
|
spec.pattern = 'spec/**/*_spec.rb'
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
require 'rubocop/rake_task'
|
11
|
+
RuboCop::RakeTask.new(:rubocop)
|
12
|
+
|
13
|
+
task default: %i[rubocop spec]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.5
|
data/lib/mongoid_search.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'mongoid_search/mongoid_search'
|
4
2
|
|
5
|
-
if defined?(Rails)
|
6
|
-
require 'mongoid_search/railtie'
|
7
|
-
end
|
3
|
+
require 'mongoid_search/railtie' if defined?(Rails)
|
8
4
|
|
9
5
|
module Mongoid::Search
|
10
6
|
## Default matching type. Match :any or :all searched keywords
|
@@ -25,7 +21,7 @@ module Mongoid::Search
|
|
25
21
|
|
26
22
|
## Stem procedure
|
27
23
|
mattr_accessor :stem_proc
|
28
|
-
@@stem_proc =
|
24
|
+
@@stem_proc = proc { |word| word.stem }
|
29
25
|
|
30
26
|
## Words to ignore
|
31
27
|
mattr_accessor :ignore_list
|
@@ -45,7 +41,7 @@ module Mongoid::Search
|
|
45
41
|
mattr_accessor :regex
|
46
42
|
|
47
43
|
## Match partial words on both sides (slower)
|
48
|
-
@@regex =
|
44
|
+
@@regex = proc { |query| /#{query}/ }
|
49
45
|
|
50
46
|
## Match partial words on the beginning or in the end (slightly faster)
|
51
47
|
# @@regex = Proc.new { |query| /^#{query}/ }
|
@@ -54,7 +50,7 @@ module Mongoid::Search
|
|
54
50
|
# Ligatures to be replaced
|
55
51
|
# http://en.wikipedia.org/wiki/Typographic_ligature
|
56
52
|
mattr_accessor :ligatures
|
57
|
-
@@ligatures = {
|
53
|
+
@@ligatures = { 'œ' => 'oe', 'æ' => 'ae' }
|
58
54
|
|
59
55
|
# Minimum word size. Words smaller than it won't be indexed
|
60
56
|
mattr_accessor :minimum_word_size
|
data/lib/mongoid_search/log.rb
CHANGED
@@ -15,16 +15,16 @@ module Mongoid::Search
|
|
15
15
|
# Set a field or a number of fields as sources for search
|
16
16
|
def search_in(*args)
|
17
17
|
args, _options = args_and_options(args)
|
18
|
-
self.search_fields = (
|
18
|
+
self.search_fields = (search_fields || []).concat args
|
19
19
|
|
20
|
-
field :_keywords, :
|
20
|
+
field :_keywords, type: Array
|
21
21
|
|
22
|
-
index({ :
|
22
|
+
index({ _keywords: 1 }, background: true)
|
23
23
|
|
24
24
|
before_save :set_keywords
|
25
25
|
end
|
26
26
|
|
27
|
-
def full_text_search(query, options={})
|
27
|
+
def full_text_search(query, options = {})
|
28
28
|
options = extract_options(options)
|
29
29
|
attr_accessor :relevance if options[:relevant_search].eql? true
|
30
30
|
|
@@ -44,34 +44,37 @@ module Mongoid::Search
|
|
44
44
|
# Goes through all documents in the class that includes Mongoid::Search
|
45
45
|
# and indexes the keywords.
|
46
46
|
def index_keywords!
|
47
|
-
all.each { |d| d.index_keywords! ? Log.green(
|
47
|
+
all.each { |d| d.index_keywords! ? Log.green('.') : Log.red('F') }
|
48
48
|
end
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
52
|
def query(keywords, options)
|
53
53
|
keywords_hash = keywords.map do |kw|
|
54
|
-
|
55
|
-
|
54
|
+
if Mongoid::Search.regex_search
|
55
|
+
escaped_kw = Regexp.escape(kw)
|
56
|
+
kw = Mongoid::Search.regex.call(escaped_kw)
|
57
|
+
end
|
58
|
+
{ _keywords: kw }
|
56
59
|
end
|
57
60
|
|
58
|
-
criteria.send("#{(options[:match])
|
61
|
+
criteria.send("#{(options[:match])}_of", *keywords_hash)
|
59
62
|
end
|
60
63
|
|
61
64
|
def args_and_options(args)
|
62
65
|
options = args.last.is_a?(Hash) &&
|
63
|
-
[
|
64
|
-
|
65
|
-
|
66
|
+
%i[match
|
67
|
+
allow_empty_search
|
68
|
+
relevant_search].include?(args.last.keys.first) ? args.pop : {}
|
66
69
|
|
67
70
|
[args, extract_options(options)]
|
68
71
|
end
|
69
72
|
|
70
73
|
def extract_options(options)
|
71
74
|
{
|
72
|
-
:
|
73
|
-
:
|
74
|
-
:
|
75
|
+
match: options[:match] || Mongoid::Search.match,
|
76
|
+
allow_empty_search: options[:allow_empty_search] || Mongoid::Search.allow_empty_search,
|
77
|
+
relevant_search: options[:relevant_search] || Mongoid::Search.relevant_search
|
75
78
|
}
|
76
79
|
end
|
77
80
|
|
@@ -81,20 +84,18 @@ module Mongoid::Search
|
|
81
84
|
|
82
85
|
def search_relevant(query, options)
|
83
86
|
results_with_relevance(query, options).sort { |o| o['value'] }.map do |r|
|
84
|
-
|
85
|
-
new(r['_id'].merge(:relevance => r['value'])) do |o|
|
87
|
+
new(r['_id'].merge(relevance: r['value'])) do |o|
|
86
88
|
# Need to match the actual object
|
87
89
|
o.instance_variable_set('@new_record', false)
|
88
90
|
o._id = r['_id']['_id']
|
89
91
|
end
|
90
|
-
|
91
92
|
end
|
92
93
|
end
|
93
94
|
|
94
95
|
def results_with_relevance(query, options)
|
95
96
|
keywords = Mongoid::Search::Util.normalize_keywords(query)
|
96
97
|
|
97
|
-
map = %
|
98
|
+
map = %{
|
98
99
|
function() {
|
99
100
|
var entries = 0;
|
100
101
|
for(i in keywords) {
|
@@ -110,13 +111,13 @@ module Mongoid::Search
|
|
110
111
|
}
|
111
112
|
}
|
112
113
|
|
113
|
-
reduce = %
|
114
|
+
reduce = %{
|
114
115
|
function(key, values) {
|
115
116
|
return(values);
|
116
117
|
}
|
117
118
|
}
|
118
119
|
|
119
|
-
query(keywords, options).map_reduce(map, reduce).scope(:
|
120
|
+
query(keywords, options).map_reduce(map, reduce).scope(keywords: keywords).out(inline: 1)
|
120
121
|
end
|
121
122
|
end
|
122
123
|
|
@@ -125,7 +126,7 @@ module Mongoid::Search
|
|
125
126
|
end
|
126
127
|
|
127
128
|
def set_keywords
|
128
|
-
self._keywords = Mongoid::Search::Util.keywords(self,
|
129
|
-
|
129
|
+
self._keywords = Mongoid::Search::Util.keywords(self, search_fields)
|
130
|
+
.flatten.reject { |k| k.nil? || k.empty? }.uniq.sort
|
130
131
|
end
|
131
132
|
end
|