mongoid-haystack 1.1.0 → 1.2.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/README.md +82 -30
- data/lib/mongoid-haystack/index.rb +45 -13
- data/lib/mongoid-haystack/search.rb +185 -77
- data/lib/mongoid-haystack/token.rb +53 -12
- data/lib/mongoid-haystack/util.rb +4 -1
- data/lib/mongoid-haystack.rb +16 -7
- data/mongoid-haystack.gemspec +6 -4
- data/test/mongoid-haystack_test.rb +122 -10
- metadata +36 -6
- data/lib/app/models/mongoid/haystack/count.rb +0 -1
- data/lib/mongoid-haystack/count.rb +0 -28
data/README.md
CHANGED
|
@@ -9,6 +9,30 @@ DESCRIPTION
|
|
|
9
9
|
mongoid-haystack provides a zero-config, POLS, pure mongo, fulltext search
|
|
10
10
|
solution for your mongoid models.
|
|
11
11
|
|
|
12
|
+
INSTALL
|
|
13
|
+
-------
|
|
14
|
+
|
|
15
|
+
rubygems: gem intstall 'mongoid-haystack'
|
|
16
|
+
|
|
17
|
+
Gemfile: gem 'mongoid-haystack'
|
|
18
|
+
|
|
19
|
+
rake db:mongoid:create_indexes # IMPORTANT
|
|
20
|
+
|
|
21
|
+
````ruby
|
|
22
|
+
|
|
23
|
+
# you might want this in lib/tasks/db.rake ...
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
namespace :db do
|
|
27
|
+
namespace :mongoid do
|
|
28
|
+
task :create_indexes do
|
|
29
|
+
Mongoid::Haystack.create_indexes
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
````
|
|
35
|
+
|
|
12
36
|
SYNOPSIS
|
|
13
37
|
--------
|
|
14
38
|
|
|
@@ -29,6 +53,32 @@ SYNOPSIS
|
|
|
29
53
|
|
|
30
54
|
article = results.first.model
|
|
31
55
|
|
|
56
|
+
# by default 'search' returns a Mongoid::Criteria object. the result set will
|
|
57
|
+
# be full of objects that refer to a model in your app via a polymorphic
|
|
58
|
+
# relation out. aka
|
|
59
|
+
#
|
|
60
|
+
# Article.search('foobar').first.class #=> Mongoid::Haystack::Index
|
|
61
|
+
# Article.search('foobar').first.model.class #=> Article
|
|
62
|
+
#
|
|
63
|
+
# in an index view you are not going to want to expand the search index
|
|
64
|
+
# objects into full blown models one at the time (N+1) so you can use the
|
|
65
|
+
# 'models' method on the collection to effciently expand the collection into
|
|
66
|
+
# your application models with the fewest possible queries. note that
|
|
67
|
+
# 'models' is a terminal operator. that is to say it returns an array and,
|
|
68
|
+
# afterwards, no more fancy query language is gonna work.
|
|
69
|
+
#
|
|
70
|
+
@results =
|
|
71
|
+
Mongoid::Haystack.search('needle').models
|
|
72
|
+
|
|
73
|
+
# pagination is supported *out of the box*. note that you should chain it
|
|
74
|
+
# *b4* any call to 'models' as 'models' is a terminal operator: it returns
|
|
75
|
+
# an array and *not* a Mongoid::Criteria object
|
|
76
|
+
#
|
|
77
|
+
@models =
|
|
78
|
+
Mongoid::Haystack.search('needle').
|
|
79
|
+
paginate(:page => 3, :size => 42).
|
|
80
|
+
models
|
|
81
|
+
|
|
32
82
|
|
|
33
83
|
# haystack stems the search terms and does score based sorting all using a
|
|
34
84
|
# fast b-tree
|
|
@@ -44,10 +94,11 @@ SYNOPSIS
|
|
|
44
94
|
results == [a] #=> true
|
|
45
95
|
|
|
46
96
|
|
|
47
|
-
# cross
|
|
48
|
-
# customise how they are indexed:
|
|
97
|
+
# cross model searching (site search)is supported out of the box, and models
|
|
98
|
+
# can customise how they are indexed:
|
|
49
99
|
#
|
|
50
100
|
# - a global score lets some models appear hight in the global results
|
|
101
|
+
#
|
|
51
102
|
# - keywords count more than fulltext
|
|
52
103
|
#
|
|
53
104
|
class Article
|
|
@@ -89,19 +140,8 @@ SYNOPSIS
|
|
|
89
140
|
models == [a1, a2] #=> true. because keywords score highter than general fulltext
|
|
90
141
|
|
|
91
142
|
|
|
92
|
-
# by default searching returns Mongoid::Haystack::Index objects. you'll want
|
|
93
|
-
# to expand these results to the models they reference in your views, but
|
|
94
|
-
# avoid doing an N+1 query. to do this simply call #models on the result set
|
|
95
|
-
# and the models will be eager loaded using only as many queries as their are
|
|
96
|
-
# model types in your result set
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
@results = Mongoid::Haystack.search('needle').page(params[:page]).per(10)
|
|
100
|
-
@models = @results.models
|
|
101
|
-
|
|
102
|
-
|
|
103
143
|
# you can decorate your search items with arbirtrary meta data and filter
|
|
104
|
-
# searches by it later. this too uses a b-tree index.
|
|
144
|
+
# searches by it later. this too uses a speedy b-tree index.
|
|
105
145
|
#
|
|
106
146
|
class Article
|
|
107
147
|
include Mongoid::Document
|
|
@@ -128,7 +168,8 @@ SYNOPSIS
|
|
|
128
168
|
:content => 'seen the needles and the damage done...'
|
|
129
169
|
)
|
|
130
170
|
|
|
131
|
-
|
|
171
|
+
articles_for_teh_author =
|
|
172
|
+
Article.search('needle', :facets => {:author_id => author.id})
|
|
132
173
|
|
|
133
174
|
|
|
134
175
|
````
|
|
@@ -136,14 +177,16 @@ SYNOPSIS
|
|
|
136
177
|
DESCRIPTION
|
|
137
178
|
-----------
|
|
138
179
|
|
|
139
|
-
there two main pathways to understand in the code.
|
|
140
|
-
|
|
180
|
+
there two main pathways to understand in the code.
|
|
181
|
+
|
|
182
|
+
1) shit going into the into the index.
|
|
183
|
+
2) shit coming out of the index.
|
|
141
184
|
|
|
142
185
|
shit going in entails:
|
|
143
186
|
|
|
144
|
-
- stem and stopword the search terms
|
|
187
|
+
- stem and stopword the search terms
|
|
145
188
|
- create or update a new token for each
|
|
146
|
-
- create an index item
|
|
189
|
+
- create an index item referening all the tokens with precomputed scores
|
|
147
190
|
|
|
148
191
|
for example the terms 'dog dogs cat' might result in these tokens
|
|
149
192
|
|
|
@@ -166,7 +209,7 @@ for example the terms 'dog dogs cat' might result in these tokens
|
|
|
166
209
|
|
|
167
210
|
````
|
|
168
211
|
|
|
169
|
-
and this index item
|
|
212
|
+
being created|updated and this index item
|
|
170
213
|
|
|
171
214
|
|
|
172
215
|
````javascript
|
|
@@ -195,25 +238,34 @@ for example the terms 'dog dogs cat' might result in these tokens
|
|
|
195
238
|
|
|
196
239
|
being built
|
|
197
240
|
|
|
198
|
-
|
|
199
|
-
|
|
241
|
+
|
|
242
|
+
some other information is tracked, but the two normal mongoid models
|
|
243
|
+
|
|
244
|
+
- Mongoid::Haystack::Token
|
|
245
|
+
- Mongoid::Haystack::Index
|
|
246
|
+
|
|
247
|
+
are simple to look at and compromise 80% of the library functionality.
|
|
200
248
|
|
|
201
249
|
|
|
202
250
|
|
|
203
251
|
a few things to notice:
|
|
204
252
|
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
|
|
253
|
+
- tokens are counted and auto-id'd using hex notation and a sequence
|
|
254
|
+
generator. the reason for this is so that their ids are legit hash keys in
|
|
255
|
+
the keyword and fulltext score hashes (they are also smaller than 12 byte
|
|
256
|
+
object_ids or the words themselves). aka this sort can be contructed:
|
|
257
|
+
|
|
258
|
+
````ruby
|
|
259
|
+
order_by('keyword_scores.0x1' => :desc, 'keyword_scores.0x.1' => :desc)
|
|
260
|
+
````
|
|
208
261
|
|
|
209
262
|
- the data structure above allows both filtering for index items that have
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
used.
|
|
263
|
+
certain tokens, but also ordering them based on global, keyword, and fulltext
|
|
264
|
+
score without resorting to map-reduce: a b-tree index can be used.
|
|
213
265
|
|
|
214
266
|
- all tokens have their text/stem stored exactly once. aka: we do not store
|
|
215
|
-
|
|
216
|
-
|
|
267
|
+
'hugewords' all over the place but store it once and count occurances of it to
|
|
268
|
+
keep the total index much smaller
|
|
217
269
|
|
|
218
270
|
|
|
219
271
|
|
|
@@ -27,9 +27,14 @@ module Mongoid
|
|
|
27
27
|
|
|
28
28
|
class << Index
|
|
29
29
|
def add(*args)
|
|
30
|
+
# we all one or more models to the index..
|
|
31
|
+
#
|
|
30
32
|
models_for(*args) do |model|
|
|
31
33
|
config = nil
|
|
32
34
|
|
|
35
|
+
# ask the model how it wants to be indexed. if it does not know,
|
|
36
|
+
# guess.
|
|
37
|
+
#
|
|
33
38
|
if model.respond_to?(:to_haystack)
|
|
34
39
|
config = Map.for(model.to_haystack)
|
|
35
40
|
else
|
|
@@ -56,41 +61,64 @@ module Mongoid
|
|
|
56
61
|
)
|
|
57
62
|
end
|
|
58
63
|
|
|
64
|
+
# blow up if no sane config was produced
|
|
65
|
+
#
|
|
66
|
+
unless %w( keywords fulltext facets score ).detect{|key| config.has_key?(key)}
|
|
67
|
+
raise ArgumentError, "you need to defined #{ model }#to_haystack"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# parse the config
|
|
71
|
+
#
|
|
59
72
|
keywords = Array(config[:keywords]).join(' ')
|
|
60
73
|
fulltext = Array(config[:fulltext]).join(' ')
|
|
61
74
|
facets = Map.for(config[:facets] || {})
|
|
62
75
|
score = config[:score]
|
|
63
76
|
|
|
77
|
+
# find or create an index item for this model
|
|
78
|
+
#
|
|
64
79
|
index =
|
|
65
80
|
Haystack.find_or_create(
|
|
66
81
|
->{ where(:model => model).first },
|
|
67
82
|
->{ new(:model => model) },
|
|
68
83
|
)
|
|
69
84
|
|
|
85
|
+
# if we are updating an index we need to decrement old token counts
|
|
86
|
+
# before updating it
|
|
87
|
+
#
|
|
70
88
|
if index.persisted?
|
|
71
89
|
Index.subtract(index)
|
|
72
90
|
end
|
|
73
91
|
|
|
92
|
+
# add tokens for both keywords and fulltext. increment counts for
|
|
93
|
+
# both.
|
|
94
|
+
#
|
|
74
95
|
keyword_scores = Hash.new{|h,k| h[k] = 0}
|
|
75
96
|
fulltext_scores = Hash.new{|h,k| h[k] = 0}
|
|
76
97
|
token_ids = []
|
|
77
98
|
|
|
78
|
-
Token.values_for(keywords)
|
|
79
|
-
|
|
99
|
+
values = Token.values_for(keywords)
|
|
100
|
+
tokens = Token.add(values)
|
|
101
|
+
token_index = tokens.inject({}){|hash, token| hash[token.value] = token; hash}
|
|
102
|
+
values.each do |value|
|
|
103
|
+
token = token_index.fetch(value)
|
|
80
104
|
id = token.id
|
|
81
|
-
|
|
82
105
|
token_ids.push(id)
|
|
83
106
|
keyword_scores[id] += 1
|
|
84
107
|
end
|
|
85
108
|
|
|
86
|
-
Token.values_for(fulltext)
|
|
87
|
-
|
|
109
|
+
values = Token.values_for(fulltext)
|
|
110
|
+
tokens = Token.add(values)
|
|
111
|
+
token_index = tokens.inject({}){|hash, token| hash[token.value] = token; hash}
|
|
112
|
+
values.each do |value|
|
|
113
|
+
token = token_index.fetch(value)
|
|
88
114
|
id = token.id
|
|
89
|
-
|
|
90
115
|
token_ids.push(id)
|
|
91
116
|
fulltext_scores[id] += 1
|
|
92
117
|
end
|
|
93
118
|
|
|
119
|
+
# our index item is complete with list of tokens, counts of each
|
|
120
|
+
# one, and a facet hash for this model
|
|
121
|
+
#
|
|
94
122
|
index.keyword_scores = keyword_scores
|
|
95
123
|
index.fulltext_scores = fulltext_scores
|
|
96
124
|
|
|
@@ -113,19 +141,23 @@ module Mongoid
|
|
|
113
141
|
def subtract(index)
|
|
114
142
|
tokens = index.tokens
|
|
115
143
|
|
|
116
|
-
|
|
144
|
+
counts = {}
|
|
117
145
|
|
|
118
146
|
tokens.each do |token|
|
|
119
147
|
keyword_score = index.keyword_scores[token.id].to_i
|
|
120
148
|
fulltext_score = index.fulltext_scores[token.id].to_i
|
|
121
149
|
|
|
122
|
-
|
|
123
|
-
token.inc(:count, -i)
|
|
150
|
+
count = keyword_score + fulltext_score
|
|
124
151
|
|
|
125
|
-
|
|
152
|
+
counts[count] ||= []
|
|
153
|
+
counts[count].push(token.id)
|
|
126
154
|
end
|
|
127
155
|
|
|
128
|
-
|
|
156
|
+
counts.each do |count, token_ids|
|
|
157
|
+
Token.where(:id.in => token_ids).inc(:count, -count)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
tokens
|
|
129
161
|
end
|
|
130
162
|
|
|
131
163
|
def models_for(*args, &block)
|
|
@@ -147,13 +179,13 @@ module Mongoid
|
|
|
147
179
|
belongs_to(:model, :polymorphic => true)
|
|
148
180
|
|
|
149
181
|
has_and_belongs_to_many(:tokens, :class_name => '::Mongoid::Haystack::Token', :inverse_of => nil)
|
|
182
|
+
|
|
150
183
|
field(:score, :type => Integer, :default => 0)
|
|
151
184
|
field(:keyword_scores, :type => Hash, :default => proc{ Hash.new{|h,k| h[k] = 0} })
|
|
152
185
|
field(:fulltext_scores, :type => Hash, :default => proc{ Hash.new{|h,k| h[k] = 0} })
|
|
153
186
|
field(:facets, :type => Hash, :default => {})
|
|
154
187
|
|
|
155
|
-
index({:model_type => 1})
|
|
156
|
-
index({:model_id => 1})
|
|
188
|
+
index({:model_type => 1, :model_id => 1}, :unique => true)
|
|
157
189
|
|
|
158
190
|
index({:token_ids => 1})
|
|
159
191
|
index({:score => 1})
|
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
module Mongoid
|
|
2
2
|
module Haystack
|
|
3
|
+
module Search
|
|
4
|
+
ClassMethods = proc do
|
|
5
|
+
def search(*args, &block)
|
|
6
|
+
options = Map.options_for!(args)
|
|
7
|
+
options[:types] = Array(options[:types]).flatten.compact
|
|
8
|
+
options[:types].push(self)
|
|
9
|
+
args.push(options)
|
|
10
|
+
results = Haystack.search(*args, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def search_index_all!
|
|
14
|
+
all.each do |doc|
|
|
15
|
+
Mongoid::Haystack::Index.remove(doc)
|
|
16
|
+
Mongoid::Haystack::Index.add(doc)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
after_save do |doc|
|
|
21
|
+
begin
|
|
22
|
+
doc.search_index! if doc.persisted?
|
|
23
|
+
rescue Object
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
after_destroy do |doc|
|
|
29
|
+
begin
|
|
30
|
+
doc.search_unindex! if doc.destroyed?
|
|
31
|
+
rescue Object
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
has_one(:haystack_index, :as => :model, :class_name => '::Mongoid::Haystack::Index')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
InstanceMethods = proc do
|
|
40
|
+
def search_index!
|
|
41
|
+
doc = self
|
|
42
|
+
Mongoid::Haystack::Index.remove(doc)
|
|
43
|
+
Mongoid::Haystack::Index.add(doc)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def search_unindex!
|
|
47
|
+
doc = self
|
|
48
|
+
Mongoid::Haystack::Index.remove(doc)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def Search.included(other)
|
|
53
|
+
super
|
|
54
|
+
ensure
|
|
55
|
+
other.instance_eval(&ClassMethods)
|
|
56
|
+
other.class_eval(&InstanceMethods)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
3
60
|
def search(*args, &block)
|
|
4
61
|
#
|
|
5
62
|
options = Map.options_for!(args)
|
|
@@ -14,15 +71,15 @@ module Mongoid
|
|
|
14
71
|
case
|
|
15
72
|
when options[:all]
|
|
16
73
|
op = :token_ids.all
|
|
17
|
-
search += Coerce.string(options[:all])
|
|
74
|
+
search += Coerce.string(options[:all])
|
|
18
75
|
|
|
19
76
|
when options[:any]
|
|
20
77
|
op = :token_ids.in
|
|
21
|
-
search += Coerce.string(options[:any])
|
|
78
|
+
search += Coerce.string(options[:any])
|
|
22
79
|
|
|
23
80
|
when options[:in]
|
|
24
81
|
op = :token_ids.in
|
|
25
|
-
search += Coerce.string(options[:in])
|
|
82
|
+
search += Coerce.string(options[:in])
|
|
26
83
|
end
|
|
27
84
|
|
|
28
85
|
#
|
|
@@ -55,114 +112,154 @@ module Mongoid
|
|
|
55
112
|
end
|
|
56
113
|
|
|
57
114
|
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
115
|
+
query =
|
|
116
|
+
Index.where(conditions)
|
|
117
|
+
.order_by(order)
|
|
118
|
+
.only(:_id, :model_type, :model_id)
|
|
62
119
|
|
|
63
|
-
|
|
64
|
-
values = Token.values_for(search.to_s)
|
|
65
|
-
tokens = Token.where(:value.in => values).to_a
|
|
66
|
-
|
|
67
|
-
positions = {}
|
|
68
|
-
tokens.each_with_index{|token, index| positions[token] = index + 1}
|
|
120
|
+
query.extend(Pagination)
|
|
69
121
|
|
|
70
|
-
|
|
122
|
+
query.extend(Denormalization)
|
|
71
123
|
|
|
72
|
-
|
|
73
|
-
[b.rarity_bin(t), positions[b]] <=> [a.rarity_bin(t), positions[a]]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
tokens
|
|
124
|
+
query
|
|
77
125
|
end
|
|
78
126
|
|
|
79
|
-
module
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
options[:types] = Array(options[:types]).flatten.compact
|
|
84
|
-
options[:types].push(self)
|
|
85
|
-
args.push(options)
|
|
86
|
-
results = Haystack.search(*args, &block)
|
|
87
|
-
end
|
|
127
|
+
module Pagination
|
|
128
|
+
def paginate(*args, &block)
|
|
129
|
+
list = self
|
|
130
|
+
options = Map.options_for!(args)
|
|
88
131
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
132
|
+
page = Integer(args.shift || options[:page] || 1)
|
|
133
|
+
size = Integer(args.shift || options[:size] || 42)
|
|
134
|
+
|
|
135
|
+
count =
|
|
136
|
+
if list.is_a?(Array)
|
|
137
|
+
list.size
|
|
138
|
+
else
|
|
139
|
+
list.count
|
|
94
140
|
end
|
|
95
|
-
end
|
|
96
141
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
limit = size
|
|
143
|
+
skip = (page - 1 ) * size
|
|
144
|
+
|
|
145
|
+
result =
|
|
146
|
+
if list.is_a?(Array)
|
|
147
|
+
list.slice(skip, limit)
|
|
148
|
+
else
|
|
149
|
+
list.skip(skip).limit(limit)
|
|
102
150
|
end
|
|
103
|
-
end
|
|
104
151
|
|
|
105
|
-
|
|
152
|
+
result._paginated.update(
|
|
153
|
+
:total_pages => (count / size.to_f).ceil,
|
|
154
|
+
:num_pages => (count / size.to_f).ceil,
|
|
155
|
+
:current_page => page
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
result
|
|
106
159
|
end
|
|
107
160
|
|
|
108
|
-
|
|
161
|
+
def _paginated
|
|
162
|
+
@_paginated ||= Map.new
|
|
109
163
|
end
|
|
110
164
|
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
165
|
+
def method_missing(method, *args, &block)
|
|
166
|
+
if respond_to?(:_paginated) and _paginated.has_key?(method) and args.empty? and block.nil?
|
|
167
|
+
_paginated[method]
|
|
168
|
+
else
|
|
169
|
+
super
|
|
170
|
+
end
|
|
116
171
|
end
|
|
117
172
|
end
|
|
118
173
|
|
|
119
|
-
module
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
self
|
|
174
|
+
module Denormalization
|
|
175
|
+
def models
|
|
176
|
+
Results.for(query = self)
|
|
123
177
|
end
|
|
124
178
|
|
|
125
|
-
def
|
|
126
|
-
denormalize
|
|
127
|
-
map(&:model)
|
|
179
|
+
def _denormalized
|
|
180
|
+
@_denormalized ||= (is_a?(Mongoid::Criteria) ? ::Mongoid::Haystack.denormalize(self) : self)
|
|
128
181
|
end
|
|
182
|
+
|
|
183
|
+
class Results < ::Array
|
|
184
|
+
include ::Mongoid::Haystack::Pagination
|
|
185
|
+
|
|
186
|
+
attr_accessor :query
|
|
187
|
+
|
|
188
|
+
def Results.for(query)
|
|
189
|
+
Results.new.tap do |results|
|
|
190
|
+
results.query = query
|
|
191
|
+
results.replace(query._denormalized)
|
|
192
|
+
results._paginated.replace(query._paginated) rescue nil
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def models
|
|
197
|
+
self
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def search_tokens_for(search)
|
|
203
|
+
values = Token.values_for(search.to_s)
|
|
204
|
+
tokens = Token.where(:value.in => values).to_a
|
|
205
|
+
|
|
206
|
+
positions = {}
|
|
207
|
+
tokens.each_with_index{|token, index| positions[token] = index + 1}
|
|
208
|
+
|
|
209
|
+
total = Token.total.to_f
|
|
210
|
+
|
|
211
|
+
tokens.sort! do |a,b|
|
|
212
|
+
[b.rarity_bin(total), positions[b]] <=> [a.rarity_bin(total), positions[a]]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
tokens
|
|
129
216
|
end
|
|
130
217
|
|
|
131
218
|
def Haystack.denormalize(results)
|
|
132
219
|
queries = Hash.new{|h,k| h[k] = []}
|
|
133
220
|
|
|
134
|
-
results = results.to_a.flatten.compact
|
|
135
|
-
|
|
136
221
|
results.each do |result|
|
|
137
222
|
model_type = result[:model_type]
|
|
138
223
|
model_id = result[:model_id]
|
|
139
|
-
model_class = model_type
|
|
224
|
+
model_class = eval(model_type) rescue next
|
|
140
225
|
queries[model_class].push(model_id)
|
|
141
226
|
end
|
|
142
227
|
|
|
228
|
+
=begin
|
|
143
229
|
index = Hash.new{|h,k| h[k] = {}}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
230
|
+
=end
|
|
231
|
+
|
|
232
|
+
models =
|
|
233
|
+
queries.map do |model_class, model_ids|
|
|
234
|
+
model_class_models =
|
|
235
|
+
begin
|
|
236
|
+
model_class.find(model_ids)
|
|
237
|
+
rescue Mongoid::Errors::DocumentNotFound
|
|
238
|
+
model_ids.map do |model_id|
|
|
239
|
+
begin
|
|
240
|
+
model_class.find(model_id)
|
|
241
|
+
rescue Mongoid::Errors::DocumentNotFound
|
|
242
|
+
nil
|
|
243
|
+
end
|
|
155
244
|
end
|
|
156
245
|
end
|
|
246
|
+
|
|
247
|
+
=begin
|
|
248
|
+
model_class_models.each do |model|
|
|
249
|
+
index[model.class.name] ||= Hash.new
|
|
250
|
+
next unless model
|
|
251
|
+
index[model.class.name][model.id.to_s] = model
|
|
157
252
|
end
|
|
253
|
+
=end
|
|
158
254
|
|
|
159
|
-
|
|
160
|
-
index[model.class.name] ||= Hash.new
|
|
161
|
-
next unless model
|
|
162
|
-
index[model.class.name][model.id.to_s] = model
|
|
255
|
+
model_class_models
|
|
163
256
|
end
|
|
164
|
-
end
|
|
165
257
|
|
|
258
|
+
models.flatten!
|
|
259
|
+
models.compact!
|
|
260
|
+
models
|
|
261
|
+
|
|
262
|
+
=begin
|
|
166
263
|
to_ignore = []
|
|
167
264
|
|
|
168
265
|
results.each_with_index do |result, i|
|
|
@@ -175,13 +272,24 @@ module Mongoid
|
|
|
175
272
|
result.model = model
|
|
176
273
|
end
|
|
177
274
|
|
|
178
|
-
result.model
|
|
179
|
-
result
|
|
275
|
+
result.model
|
|
276
|
+
result
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
to_ignore.reverse.each do |index|
|
|
280
|
+
models.delete_at(index)
|
|
180
281
|
end
|
|
282
|
+
=end
|
|
181
283
|
|
|
182
|
-
|
|
284
|
+
models
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def Haystack.expand(*args, &block)
|
|
288
|
+
Haystack.denormalize(*args, &block)
|
|
289
|
+
end
|
|
183
290
|
|
|
184
|
-
|
|
291
|
+
def Haystack.models_for(*args, &block)
|
|
292
|
+
Haystack.denormalize(*args, &block)
|
|
185
293
|
end
|
|
186
294
|
end
|
|
187
295
|
end
|
|
@@ -9,17 +9,54 @@ module Mongoid
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def add(value)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
# handle a value or array of values - which may contain dups
|
|
13
|
+
#
|
|
14
|
+
values = Array(value)
|
|
15
|
+
values.flatten!
|
|
16
|
+
values.compact!
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# ensure that a token exists for each value seen
|
|
19
|
+
#
|
|
20
|
+
existing = where(:value.in => values)
|
|
21
|
+
missing = values - existing.map(&:value)
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
docs = missing.map{|value| {:_id => Token.next_hex_id, :value => value}}
|
|
24
|
+
unless docs.empty?
|
|
25
|
+
collection = mongo_session.with(:safe => false)[collection_name]
|
|
26
|
+
collection.insert(docs, [:continue_on_error])
|
|
27
|
+
end
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
# new we should have one token per uniq value
|
|
30
|
+
#
|
|
31
|
+
tokens = where(:value.in => values)
|
|
32
|
+
|
|
33
|
+
# batch update the counts on the tokens by the number of times each
|
|
34
|
+
# value was seen in the list
|
|
35
|
+
#
|
|
36
|
+
# 'dog dog' #=> increment the 'dog' token's count by 2
|
|
37
|
+
#
|
|
38
|
+
counts = {}
|
|
39
|
+
token_index = tokens.inject({}){|hash, token| hash[token.value] = token; hash}
|
|
40
|
+
value_index = values.inject({}){|hash, value| hash[value] ||= []; hash[value].push(value); hash}
|
|
41
|
+
|
|
42
|
+
values.each do |value|
|
|
43
|
+
token = token_index[value]
|
|
44
|
+
count = value_index[value].size
|
|
45
|
+
counts[count] ||= []
|
|
46
|
+
counts[count].push(token.id)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
counts.each do |count, token_ids|
|
|
50
|
+
Token.where(:id.in => token_ids).inc(:count, count)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# return an array or single token depending on whether a list or
|
|
54
|
+
# single value was added
|
|
55
|
+
#
|
|
56
|
+
value.is_a?(Array) ? tokens : tokens.first
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def subtract(tokens)
|
|
23
60
|
end
|
|
24
61
|
|
|
25
62
|
def sequence
|
|
@@ -29,6 +66,10 @@ module Mongoid
|
|
|
29
66
|
def next_hex_id
|
|
30
67
|
"0x#{ hex = sequence.next.to_s(16) }"
|
|
31
68
|
end
|
|
69
|
+
|
|
70
|
+
def total
|
|
71
|
+
sum(:count)
|
|
72
|
+
end
|
|
32
73
|
end
|
|
33
74
|
|
|
34
75
|
field(:_id, :type => String, :default => proc{ Token.next_hex_id })
|
|
@@ -38,19 +79,19 @@ module Mongoid
|
|
|
38
79
|
index({:value => 1}, {:unique => true})
|
|
39
80
|
index({:count => 1})
|
|
40
81
|
|
|
41
|
-
def frequency(n_tokens =
|
|
82
|
+
def frequency(n_tokens = Token.total.value.to_f)
|
|
42
83
|
(count / n_tokens).round(2)
|
|
43
84
|
end
|
|
44
85
|
|
|
45
|
-
def frequency_bin(n_tokens =
|
|
86
|
+
def frequency_bin(n_tokens = Token.total.value.to_f)
|
|
46
87
|
(frequency(n_tokens) * 10).truncate
|
|
47
88
|
end
|
|
48
89
|
|
|
49
|
-
def rarity(n_tokens =
|
|
90
|
+
def rarity(n_tokens = Token.total.value.to_f)
|
|
50
91
|
((n_tokens - count) / n_tokens).round(2)
|
|
51
92
|
end
|
|
52
93
|
|
|
53
|
-
def rarity_bin(n_tokens =
|
|
94
|
+
def rarity_bin(n_tokens = Token.total.value.to_f)
|
|
54
95
|
(rarity(n_tokens) * 10).truncate
|
|
55
96
|
end
|
|
56
97
|
end
|
|
@@ -5,7 +5,6 @@ module Mongoid
|
|
|
5
5
|
[
|
|
6
6
|
Mongoid::Haystack::Token,
|
|
7
7
|
Mongoid::Haystack::Index,
|
|
8
|
-
Mongoid::Haystack::Count,
|
|
9
8
|
Mongoid::Haystack::Sequence
|
|
10
9
|
]
|
|
11
10
|
end
|
|
@@ -29,6 +28,10 @@ module Mongoid
|
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
|
|
31
|
+
def create_indexes
|
|
32
|
+
models.each{|model| model.create_indexes}
|
|
33
|
+
end
|
|
34
|
+
|
|
32
35
|
def destroy_all
|
|
33
36
|
models.map{|model| model.destroy_all}
|
|
34
37
|
end
|
data/lib/mongoid-haystack.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
module Mongoid
|
|
4
4
|
module Haystack
|
|
5
|
-
const_set :Version, '1.
|
|
5
|
+
const_set :Version, '1.2.0'
|
|
6
6
|
|
|
7
7
|
class << Haystack
|
|
8
8
|
def version
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
def dependencies
|
|
13
13
|
{
|
|
14
|
-
'mongoid' => [ 'mongoid' , '~> 3.0'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
14
|
+
'mongoid' => [ 'mongoid' , '~> 3.0.14' ] ,
|
|
15
|
+
'moped' => [ 'moped' , '~> 1.3.1' ] ,
|
|
16
|
+
'origin' => [ 'origin' , '~> 1.0.11' ] ,
|
|
17
|
+
'map' => [ 'map' , '~> 6.2' ] ,
|
|
18
|
+
'fattr' => [ 'fattr' , '~> 2.2' ] ,
|
|
19
|
+
'coerce' => [ 'coerce' , '~> 0.0.3' ] ,
|
|
20
|
+
'unicode_utils' => [ 'unicode_utils' , '~> 1.4.0' ] ,
|
|
19
21
|
}
|
|
20
22
|
end
|
|
21
23
|
|
|
@@ -73,7 +75,6 @@
|
|
|
73
75
|
|
|
74
76
|
load Haystack.libdir('stemming.rb')
|
|
75
77
|
load Haystack.libdir('util.rb')
|
|
76
|
-
load Haystack.libdir('count.rb')
|
|
77
78
|
load Haystack.libdir('sequence.rb')
|
|
78
79
|
load Haystack.libdir('token.rb')
|
|
79
80
|
load Haystack.libdir('index.rb')
|
|
@@ -86,3 +87,11 @@
|
|
|
86
87
|
extend Haystack
|
|
87
88
|
end
|
|
88
89
|
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
#
|
|
93
|
+
if defined?(Rails)
|
|
94
|
+
class Mongoid::Haystack::Engine < Rails::Engine
|
|
95
|
+
paths['app/models'] = File.dirname(__FILE__)
|
|
96
|
+
end
|
|
97
|
+
end
|
data/mongoid-haystack.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Gem::Specification::new do |spec|
|
|
5
5
|
spec.name = "mongoid-haystack"
|
|
6
|
-
spec.version = "1.
|
|
6
|
+
spec.version = "1.2.0"
|
|
7
7
|
spec.platform = Gem::Platform::RUBY
|
|
8
8
|
spec.summary = "mongoid-haystack"
|
|
9
9
|
spec.description = "a mongoid 3 zero-config, zero-integration, POLS pure mongo fulltext solution"
|
|
@@ -16,13 +16,11 @@ Gem::Specification::new do |spec|
|
|
|
16
16
|
"lib/app/models",
|
|
17
17
|
"lib/app/models/mongoid",
|
|
18
18
|
"lib/app/models/mongoid/haystack",
|
|
19
|
-
"lib/app/models/mongoid/haystack/count.rb",
|
|
20
19
|
"lib/app/models/mongoid/haystack/index.rb",
|
|
21
20
|
"lib/app/models/mongoid/haystack/sequence.rb",
|
|
22
21
|
"lib/app/models/mongoid/haystack/token.rb",
|
|
23
22
|
"lib/mongoid-haystack",
|
|
24
23
|
"lib/mongoid-haystack.rb",
|
|
25
|
-
"lib/mongoid-haystack/count.rb",
|
|
26
24
|
"lib/mongoid-haystack/index.rb",
|
|
27
25
|
"lib/mongoid-haystack/search.rb",
|
|
28
26
|
"lib/mongoid-haystack/sequence.rb",
|
|
@@ -58,7 +56,11 @@ Gem::Specification::new do |spec|
|
|
|
58
56
|
spec.test_files = nil
|
|
59
57
|
|
|
60
58
|
|
|
61
|
-
spec.add_dependency(*["mongoid", "~> 3.0"])
|
|
59
|
+
spec.add_dependency(*["mongoid", "~> 3.0.14"])
|
|
60
|
+
|
|
61
|
+
spec.add_dependency(*["moped", "~> 1.3.1"])
|
|
62
|
+
|
|
63
|
+
spec.add_dependency(*["origin", "~> 1.0.11"])
|
|
62
64
|
|
|
63
65
|
spec.add_dependency(*["map", "~> 6.2"])
|
|
64
66
|
|
|
@@ -14,6 +14,17 @@ Testing Mongoid::Haystack do
|
|
|
14
14
|
assert{ Mongoid::Haystack.search('cat').map(&:model) == [b] }
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
##
|
|
18
|
+
#
|
|
19
|
+
testing 'that results are returned as chainable Mongoid::Criteria' do
|
|
20
|
+
k = new_klass
|
|
21
|
+
|
|
22
|
+
3.times{ k.create! :content => 'cats' }
|
|
23
|
+
|
|
24
|
+
results = assert{ Mongoid::Haystack.search('cat') }
|
|
25
|
+
assert{ results.is_a?(Mongoid::Criteria) }
|
|
26
|
+
end
|
|
27
|
+
|
|
17
28
|
##
|
|
18
29
|
#
|
|
19
30
|
testing 'that word occurance affects the sort' do
|
|
@@ -67,7 +78,7 @@ Testing Mongoid::Haystack do
|
|
|
67
78
|
|
|
68
79
|
assert{ Mongoid::Haystack::Token.count == 2 }
|
|
69
80
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
70
|
-
assert{ Mongoid::Haystack::
|
|
81
|
+
assert{ Mongoid::Haystack::Token.total == 3 }
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
testing 'that removing a model from the index decrements counts appropriately' do
|
|
@@ -81,27 +92,27 @@ Testing Mongoid::Haystack do
|
|
|
81
92
|
|
|
82
93
|
assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 2 }
|
|
83
94
|
assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 2 }
|
|
84
|
-
assert{ Mongoid::Haystack::
|
|
95
|
+
assert{ Mongoid::Haystack::Token.total == 4 }
|
|
85
96
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
86
97
|
assert{ Mongoid::Haystack.unindex(c) }
|
|
87
98
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
88
|
-
assert{ Mongoid::Haystack::
|
|
99
|
+
assert{ Mongoid::Haystack::Token.total == 2 }
|
|
89
100
|
assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 1 }
|
|
90
101
|
assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 1 }
|
|
91
102
|
|
|
92
|
-
assert{ Mongoid::Haystack::
|
|
103
|
+
assert{ Mongoid::Haystack::Token.total == 2 }
|
|
93
104
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
94
105
|
assert{ Mongoid::Haystack.unindex(b) }
|
|
95
106
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
96
|
-
assert{ Mongoid::Haystack::
|
|
107
|
+
assert{ Mongoid::Haystack::Token.total == 1 }
|
|
97
108
|
assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 0 }
|
|
98
109
|
assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 1 }
|
|
99
110
|
|
|
100
|
-
assert{ Mongoid::Haystack::
|
|
111
|
+
assert{ Mongoid::Haystack::Token.total == 1 }
|
|
101
112
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
102
113
|
assert{ Mongoid::Haystack.unindex(a) }
|
|
103
114
|
assert{ Mongoid::Haystack::Token.all.map(&:value).sort == %w( cat dog ) }
|
|
104
|
-
assert{ Mongoid::Haystack::
|
|
115
|
+
assert{ Mongoid::Haystack::Token.total == 0 }
|
|
105
116
|
assert{ Mongoid::Haystack::Token.where(:value => 'cat').first.count == 0 }
|
|
106
117
|
assert{ Mongoid::Haystack::Token.where(:value => 'dog').first.count == 0 }
|
|
107
118
|
end
|
|
@@ -235,20 +246,119 @@ Testing Mongoid::Haystack do
|
|
|
235
246
|
assert{ Mongoid::Haystack.search('dog').first.model == b }
|
|
236
247
|
end
|
|
237
248
|
|
|
249
|
+
##
|
|
250
|
+
#
|
|
251
|
+
testing 'that re-indexing a class is idempotent' do
|
|
252
|
+
k = new_klass do
|
|
253
|
+
field(:title)
|
|
254
|
+
field(:body)
|
|
255
|
+
|
|
256
|
+
def to_haystack
|
|
257
|
+
{ :keywords => title, :fulltext => body }
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
n = 10
|
|
262
|
+
|
|
263
|
+
n.times do
|
|
264
|
+
k.create!(:title => 'the cats and dogs', :body => 'now now is is the the time time for for all all good good men women')
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
n.times do
|
|
268
|
+
k.create!(:title => 'a b c abc xyz abc xyz b', :body => 'pdq pdq pdq xyz teh ngr am')
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
assert{ Mongoid::Haystack.search('cat').count == n }
|
|
272
|
+
assert{ Mongoid::Haystack.search('pdq').count == n }
|
|
273
|
+
|
|
274
|
+
ca = Mongoid::Haystack::Token.all.inject({}){|hash, token| hash.update token.id => token.value}
|
|
275
|
+
|
|
276
|
+
assert{ k.search_index_all! }
|
|
277
|
+
|
|
278
|
+
cb = Mongoid::Haystack::Token.all.inject({}){|hash, token| hash.update token.id => token.value}
|
|
279
|
+
|
|
280
|
+
assert{ ca.size == Mongoid::Haystack::Token.count }
|
|
281
|
+
assert{ cb.size == Mongoid::Haystack::Token.count }
|
|
282
|
+
assert{ ca == cb }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
##
|
|
286
|
+
#
|
|
287
|
+
testing 'that not just any model can be indexed' do
|
|
288
|
+
o = new_klass.create!
|
|
289
|
+
assert{ begin; Mongoid::Haystack::Index.add(o); rescue Object => e; e.is_a?(ArgumentError); end }
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
##
|
|
293
|
+
#
|
|
294
|
+
testing 'that results can be expanded efficiently if need be' do
|
|
295
|
+
k = new_klass
|
|
296
|
+
3.times{ k.create! :content => 'cats' }
|
|
297
|
+
|
|
298
|
+
results = assert{ Mongoid::Haystack.search('cat') }
|
|
299
|
+
assert{ Mongoid::Haystack.models_for(results).map{|model| model.class} == [k, k, k] }
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
##
|
|
303
|
+
#
|
|
304
|
+
testing 'basic pagination' do
|
|
305
|
+
k = new_klass
|
|
306
|
+
11.times{|i| k.create! :content => "cats #{ i }" }
|
|
307
|
+
|
|
308
|
+
assert{ k.search('cat').paginate(:page => 1, :size => 2).to_a.size == 2 }
|
|
309
|
+
assert{ k.search('cat').paginate(:page => 2, :size => 5).to_a.size == 5 }
|
|
310
|
+
|
|
311
|
+
accum = []
|
|
312
|
+
|
|
313
|
+
n = 6
|
|
314
|
+
size = 2
|
|
315
|
+
(1..n).each do |page|
|
|
316
|
+
list = assert{ k.search('cat').paginate(:page => page, :size => size) }
|
|
317
|
+
accum.push(*list)
|
|
318
|
+
assert{ list.num_pages == n }
|
|
319
|
+
assert{ list.total_pages == n }
|
|
320
|
+
assert{ list.current_page == page }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
a = accum.map{|i| i.model}.sort_by{|m| m.content}
|
|
324
|
+
b = k.all.sort_by{|m| m.content}
|
|
325
|
+
|
|
326
|
+
assert{ a == b }
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
##
|
|
330
|
+
#
|
|
331
|
+
testing 'that pagination preserves the #model terminator' do
|
|
332
|
+
k = new_klass
|
|
333
|
+
11.times{|i| k.create! :content => "cats #{ i }" }
|
|
334
|
+
|
|
335
|
+
list = assert{ k.search('cat').paginate(:page => 1, :size => 2) }
|
|
336
|
+
assert{ list.is_a?(Mongoid::Criteria) }
|
|
337
|
+
|
|
338
|
+
models = assert{ list.models }
|
|
339
|
+
assert{ models.is_a?(Array) }
|
|
340
|
+
end
|
|
341
|
+
|
|
238
342
|
protected
|
|
239
343
|
|
|
240
344
|
def new_klass(&block)
|
|
241
|
-
|
|
345
|
+
if Object.send(:const_defined?, :K)
|
|
346
|
+
Object.const_get(:K).destroy_all
|
|
347
|
+
Object.send(:remove_const, :K)
|
|
348
|
+
end
|
|
242
349
|
|
|
243
350
|
k = Class.new(A) do
|
|
244
351
|
self.default_collection_name = :ks
|
|
245
352
|
def self.name() 'K' end
|
|
246
|
-
include ::Mongoid::Haystack::Search
|
|
247
|
-
class_eval(&block) if block
|
|
248
353
|
end
|
|
249
354
|
|
|
250
355
|
Object.const_set(:K, k)
|
|
251
356
|
|
|
357
|
+
k.class_eval do
|
|
358
|
+
include ::Mongoid::Haystack::Search
|
|
359
|
+
class_eval(&block) if block
|
|
360
|
+
end
|
|
361
|
+
|
|
252
362
|
k
|
|
253
363
|
end
|
|
254
364
|
|
|
@@ -260,4 +370,6 @@ protected
|
|
|
260
370
|
[A, B, C].map{|m| m.destroy_all}
|
|
261
371
|
Mongoid::Haystack.destroy_all
|
|
262
372
|
end
|
|
373
|
+
|
|
374
|
+
at_exit{ K.destroy_all if defined?(K) }
|
|
263
375
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongoid-haystack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-12-
|
|
12
|
+
date: 2012-12-11 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: mongoid
|
|
@@ -18,7 +18,7 @@ dependencies:
|
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version:
|
|
21
|
+
version: 3.0.14
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -26,7 +26,39 @@ dependencies:
|
|
|
26
26
|
requirements:
|
|
27
27
|
- - ~>
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version:
|
|
29
|
+
version: 3.0.14
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: moped
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: 1.3.1
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 1.3.1
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: origin
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ~>
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 1.0.11
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 1.0.11
|
|
30
62
|
- !ruby/object:Gem::Dependency
|
|
31
63
|
name: map
|
|
32
64
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -99,12 +131,10 @@ extra_rdoc_files: []
|
|
|
99
131
|
files:
|
|
100
132
|
- README.md
|
|
101
133
|
- Rakefile
|
|
102
|
-
- lib/app/models/mongoid/haystack/count.rb
|
|
103
134
|
- lib/app/models/mongoid/haystack/index.rb
|
|
104
135
|
- lib/app/models/mongoid/haystack/sequence.rb
|
|
105
136
|
- lib/app/models/mongoid/haystack/token.rb
|
|
106
137
|
- lib/mongoid-haystack.rb
|
|
107
|
-
- lib/mongoid-haystack/count.rb
|
|
108
138
|
- lib/mongoid-haystack/index.rb
|
|
109
139
|
- lib/mongoid-haystack/search.rb
|
|
110
140
|
- lib/mongoid-haystack/sequence.rb
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Mongoid::Haystack::Count
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
module Mongoid
|
|
2
|
-
module Haystack
|
|
3
|
-
class Count
|
|
4
|
-
include Mongoid::Document
|
|
5
|
-
|
|
6
|
-
field(:name, :type => String)
|
|
7
|
-
field(:value, :type => Integer, :default => 0)
|
|
8
|
-
|
|
9
|
-
index({:name => 1}, {:unique => true})
|
|
10
|
-
index({:value => 1})
|
|
11
|
-
|
|
12
|
-
def Count.for(name)
|
|
13
|
-
Haystack.find_or_create(
|
|
14
|
-
->{ where(:name => name.to_s).first },
|
|
15
|
-
->{ create!(:name => name.to_s) }
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def Count.[](name)
|
|
20
|
-
Count.for(name)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def inc(n = 1)
|
|
24
|
-
super(:value, n)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|