mongoid-fts 0.5.0 → 1.0.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.
- checksums.yaml +8 -8
- data/README.md +12 -3
- data/lib/mongoid-fts.rb +203 -50
- data/mongoid-fts.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzRkOTk2NjNkYmQ5ZjM2NjU3YzkxYTM3N2I1MzRhNTFjZjAzMzAwMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YjUzZGEyOWE2YWViZGRmZmQ4NWJjNTgxZTc4ZGM1MjUyYWI3MDBjMw==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTEyMWRkYmQ1NWM5YTVmZmJlOGUzOGI4NmFiZmU3M2UzNzRjNjg2MmE5NDEx
|
10
|
+
NjUxODE1YzE3NDJmMjJmNTZkZjFhZTljMzkzMDNmZDhmY2NkNDAyNTJhZmMy
|
11
|
+
MzM0MmFjNGFkMGMwZmYxNDU2MmRkNWVjM2JkMDM0MWFjMTY5OWQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NWE3OGY3MDcwNTE1ZDQ0MmVmYWFhZGE5YmJjZDJlMzFlZGRjM2ZiNTM5ODQw
|
14
|
+
MmVhMmNhMGQ5NWJiNmYwOTAwYWY4NzgwNzNhNGRiYzUxY2NjZGU2NzI2OTBi
|
15
|
+
NzczZjA2OWQzYWMwN2UwODBjZTJmNzY1NTMzYmFjZjk2YTVhYjY=
|
data/README.md
CHANGED
@@ -4,8 +4,14 @@ NAME
|
|
4
4
|
|
5
5
|
DESCRIPTION
|
6
6
|
|
7
|
-
enable mongodb's new fulltext simply and quickly on your mongoid models
|
7
|
+
enable mongodb's new fulltext simply and quickly on your mongoid models.
|
8
8
|
|
9
|
+
supports
|
10
|
+
* pagination
|
11
|
+
* strict literal searching (including stopwords)
|
12
|
+
* cross models searching
|
13
|
+
* index is automatically kept in sync
|
14
|
+
* customize ranking with #to_search
|
9
15
|
|
10
16
|
INSTALL
|
11
17
|
|
@@ -59,7 +65,7 @@ SYNOPSIS
|
|
59
65
|
field(:c)
|
60
66
|
|
61
67
|
def to_search
|
62
|
-
{:title => a, :keywords => (b + ['foobar']), :fulltext => c}
|
68
|
+
{:literals => [id, sku], :title => a, :keywords => (b + ['foobar']), :fulltext => c}
|
63
69
|
end
|
64
70
|
end
|
65
71
|
|
@@ -92,6 +98,10 @@ SYNOPSIS
|
|
92
98
|
|
93
99
|
Mongoid::FTS::Index.reset! # completely drop/create indexes - lose all objects
|
94
100
|
|
101
|
+
Mongoid::FTS.index(model) # add an object to the fts index
|
102
|
+
|
103
|
+
Mongoid::FTS.unindex(model) # remove and object from the fts index
|
104
|
+
|
95
105
|
````
|
96
106
|
|
97
107
|
the implementation has a temporary work around for pagination, see
|
@@ -100,7 +110,6 @@ the implementation has a temporary work around for pagination, see
|
|
100
110
|
|
101
111
|
for details
|
102
112
|
|
103
|
-
|
104
113
|
regardless, the *interface* of this mixin is uber simple and should be quite
|
105
114
|
future proof. as the mongodb teams moves search forward i'll track the new
|
106
115
|
implementation and preserve the current interface. until it settles down,
|
data/lib/mongoid-fts.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Mongoid
|
2
2
|
module FTS
|
3
3
|
#
|
4
|
-
const_set(:Version, '0.
|
4
|
+
const_set(:Version, '1.0.0') unless const_defined?(:Version)
|
5
5
|
|
6
6
|
class << FTS
|
7
7
|
def version
|
@@ -70,7 +70,10 @@ module Mongoid
|
|
70
70
|
def FTS._search(*args)
|
71
71
|
options = Map.options_for!(args)
|
72
72
|
|
73
|
-
search
|
73
|
+
search = args.join(' ')
|
74
|
+
words = search.strip.split(/\s+/)
|
75
|
+
literals = FTS.literals_for(*words)
|
76
|
+
search = [literals, search].join(' ')
|
74
77
|
|
75
78
|
text = options.delete(:text) || Index.default_collection_name.to_s
|
76
79
|
limit = [Integer(options.delete(:limit) || 128), 1].max
|
@@ -259,18 +262,51 @@ module Mongoid
|
|
259
262
|
|
260
263
|
belongs_to(:context, :polymorphic => true)
|
261
264
|
|
265
|
+
field(:literals, :type => Array)
|
266
|
+
|
267
|
+
field(:literal_title, :type => String)
|
262
268
|
field(:title, :type => String)
|
269
|
+
|
270
|
+
field(:literal_keywords, :type => Array)
|
263
271
|
field(:keywords, :type => Array)
|
272
|
+
|
264
273
|
field(:fulltext, :type => String)
|
265
274
|
|
266
275
|
index(
|
267
|
-
{
|
268
|
-
|
276
|
+
{
|
277
|
+
:context_type => 1,
|
278
|
+
:context_id => 1
|
279
|
+
},
|
280
|
+
|
281
|
+
{
|
282
|
+
:unique => true,
|
283
|
+
:sparse => true
|
284
|
+
}
|
269
285
|
)
|
270
286
|
|
271
287
|
index(
|
272
|
-
{
|
273
|
-
|
288
|
+
{
|
289
|
+
:context_type => 1,
|
290
|
+
:literals => 'text',
|
291
|
+
:literal_title => 'text',
|
292
|
+
:title => 'text',
|
293
|
+
:literal_keywords => 'text',
|
294
|
+
:keywords => 'text',
|
295
|
+
:fulltext => 'text'
|
296
|
+
},
|
297
|
+
|
298
|
+
{
|
299
|
+
:name => 'search_index',
|
300
|
+
|
301
|
+
:weights => {
|
302
|
+
:literals => 200,
|
303
|
+
:literal_title => 100,
|
304
|
+
:title => 90,
|
305
|
+
:literal_keywords => 60,
|
306
|
+
:keywords => 50,
|
307
|
+
:fulltext => 1
|
308
|
+
}
|
309
|
+
}
|
274
310
|
)
|
275
311
|
|
276
312
|
before_validation do |index|
|
@@ -281,6 +317,10 @@ module Mongoid
|
|
281
317
|
index.normalize
|
282
318
|
end
|
283
319
|
|
320
|
+
before_save do |index|
|
321
|
+
index.normalize
|
322
|
+
end
|
323
|
+
|
284
324
|
validates_presence_of(:context_type)
|
285
325
|
|
286
326
|
def normalize
|
@@ -292,16 +332,24 @@ module Mongoid
|
|
292
332
|
def normalize!
|
293
333
|
index = self
|
294
334
|
|
295
|
-
unless [index.
|
296
|
-
index.
|
335
|
+
unless [index.literals].join.strip.empty?
|
336
|
+
index.literals = FTS.list_of_strings(index.literals)
|
297
337
|
end
|
298
338
|
|
299
339
|
unless [index.title].join.strip.empty?
|
300
340
|
index.title = index.title.to_s.strip
|
301
341
|
end
|
302
342
|
|
343
|
+
unless [index.literal_title].join.strip.empty?
|
344
|
+
index.literal_title = index.literal_title.to_s.strip
|
345
|
+
end
|
346
|
+
|
303
347
|
unless [index.keywords].join.strip.empty?
|
304
|
-
index.keywords = index.keywords
|
348
|
+
index.keywords = FTS.list_of_strings(index.keywords)
|
349
|
+
end
|
350
|
+
|
351
|
+
unless [index.literal_keywords].join.strip.empty?
|
352
|
+
index.literal_keywords = FTS.list_of_strings(index.literal_keywords)
|
305
353
|
end
|
306
354
|
|
307
355
|
unless [index.fulltext].join.strip.empty?
|
@@ -312,6 +360,10 @@ module Mongoid
|
|
312
360
|
@normalized = true
|
313
361
|
end
|
314
362
|
|
363
|
+
def inspect(*args, &block)
|
364
|
+
Map.for(as_document).inspect(*args, &block)
|
365
|
+
end
|
366
|
+
|
315
367
|
def Index.teardown!
|
316
368
|
Index.remove_indexes
|
317
369
|
Index.destroy_all
|
@@ -342,12 +394,18 @@ module Mongoid
|
|
342
394
|
models.each{|model| add(model)}
|
343
395
|
end
|
344
396
|
|
345
|
-
def Index.add(model)
|
397
|
+
def Index.add!(model)
|
346
398
|
to_search = Index.to_search(model)
|
347
399
|
|
348
|
-
|
349
|
-
|
350
|
-
|
400
|
+
literals = to_search.has_key?(:literals) ? Coerce.list_of_strings(to_search[:literals]) : nil
|
401
|
+
|
402
|
+
title = to_search.has_key?(:title) ? Coerce.string(to_search[:title]) : nil
|
403
|
+
literal_title = to_search.has_key?(:literal_title) ? Coerce.string(to_search[:literal_title]) : nil
|
404
|
+
|
405
|
+
keywords = to_search.has_key?(:keywords) ? Coerce.list_of_strings(to_search[:keywords]) : nil
|
406
|
+
literal_keywords = to_search.has_key?(:literal_keywords) ? Coerce.list_of_strings(to_search[:literal_keywords]) : nil
|
407
|
+
|
408
|
+
fulltext = to_search.has_key?(:fulltext) ? Coerce.string(to_search[:fulltext]) : nil
|
351
409
|
|
352
410
|
context_type = model.class.name.to_s
|
353
411
|
context_id = model.id
|
@@ -358,87 +416,177 @@ module Mongoid
|
|
358
416
|
}
|
359
417
|
|
360
418
|
attributes = {
|
361
|
-
:
|
362
|
-
|
363
|
-
:
|
419
|
+
:literals => literals,
|
420
|
+
|
421
|
+
:title => title,
|
422
|
+
:literal_title => literal_title,
|
423
|
+
|
424
|
+
:keywords => keywords,
|
425
|
+
:literal_keywords => literal_keywords,
|
426
|
+
|
427
|
+
:fulltext => fulltext
|
364
428
|
}
|
365
429
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
430
|
+
index = nil
|
431
|
+
n = 42
|
432
|
+
|
433
|
+
n.times do |i|
|
434
|
+
index = where(conditions).first
|
435
|
+
break if index
|
436
|
+
|
437
|
+
begin
|
438
|
+
index = create!(conditions)
|
439
|
+
break if index
|
440
|
+
rescue Object
|
441
|
+
nil
|
378
442
|
end
|
379
|
-
end
|
380
443
|
|
381
|
-
|
382
|
-
|
383
|
-
index = where(conditions).first
|
444
|
+
sleep(rand) if i < (n - 1)
|
445
|
+
end
|
384
446
|
|
385
447
|
if index
|
386
|
-
|
448
|
+
begin
|
449
|
+
index.update_attributes!(attributes)
|
450
|
+
rescue Object
|
451
|
+
raise Error.new("failed to update index for #{ conditions.inspect }")
|
452
|
+
end
|
387
453
|
else
|
388
454
|
raise Error.new("failed to create index for #{ conditions.inspect }")
|
389
455
|
end
|
456
|
+
|
457
|
+
index
|
390
458
|
end
|
391
459
|
|
392
|
-
def Index.
|
393
|
-
|
394
|
-
|
460
|
+
def Index.add(*args, &block)
|
461
|
+
begin
|
462
|
+
add!(*args, &block)
|
463
|
+
rescue Object
|
464
|
+
false
|
465
|
+
end
|
466
|
+
end
|
395
467
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
}
|
468
|
+
def Index.remove!(*args, &block)
|
469
|
+
options = args.extract_options!.to_options!
|
470
|
+
models = args.flatten.compact
|
400
471
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
472
|
+
model_ids = {}
|
473
|
+
|
474
|
+
models.each do |model|
|
475
|
+
model_name = model.class.name.to_s
|
476
|
+
model_ids[model_name] ||= []
|
477
|
+
model_ids[model_name].push(model.id)
|
478
|
+
end
|
479
|
+
|
480
|
+
conditions = model_ids.map do |model_name, model_ids|
|
481
|
+
{:context_type => model_name, :context_id.in => model_ids}
|
482
|
+
end
|
483
|
+
|
484
|
+
any_of(conditions).destroy_all
|
485
|
+
end
|
486
|
+
|
487
|
+
def Index.remove(*args, &block)
|
488
|
+
begin
|
489
|
+
remove!(*args, &block)
|
490
|
+
rescue Object
|
491
|
+
false
|
405
492
|
end
|
406
493
|
end
|
407
494
|
|
408
495
|
def Index.to_search(model)
|
496
|
+
#
|
409
497
|
to_search = nil
|
410
498
|
|
499
|
+
#
|
411
500
|
if model.respond_to?(:to_search)
|
412
501
|
to_search = Map.for(model.to_search)
|
413
502
|
else
|
414
503
|
to_search = Map.new
|
415
504
|
|
505
|
+
to_search[:literals] =
|
506
|
+
%w( id ).map do |attr|
|
507
|
+
model.send(attr) if model.respond_to?(attr)
|
508
|
+
end
|
509
|
+
|
416
510
|
to_search[:title] =
|
417
511
|
%w( title ).map do |attr|
|
418
512
|
model.send(attr) if model.respond_to?(attr)
|
419
|
-
end
|
513
|
+
end
|
420
514
|
|
421
515
|
to_search[:keywords] =
|
422
516
|
%w( keywords tags ).map do |attr|
|
423
517
|
model.send(attr) if model.respond_to?(attr)
|
424
|
-
end
|
518
|
+
end
|
425
519
|
|
426
520
|
to_search[:fulltext] =
|
427
521
|
%w( fulltext text content body description ).map do |attr|
|
428
522
|
model.send(attr) if model.respond_to?(attr)
|
429
|
-
end
|
523
|
+
end
|
430
524
|
end
|
431
525
|
|
432
|
-
|
526
|
+
#
|
527
|
+
unless %w( literals title keywords fulltext ).detect{|key| to_search.has_key?(key)}
|
433
528
|
raise ArgumentError, "you need to define #{ model }#to_search"
|
434
529
|
end
|
435
530
|
|
531
|
+
#
|
532
|
+
literals = FTS.normalized_array(to_search[:literals])
|
533
|
+
title = FTS.normalized_array(to_search[:title])
|
534
|
+
keywords = FTS.normalized_array(to_search[:keywords])
|
535
|
+
fulltext = FTS.normalized_array(to_search[:fulltext])
|
536
|
+
|
537
|
+
#
|
538
|
+
to_search[:literals] = FTS.literals_for(literals)
|
539
|
+
|
540
|
+
to_search[:literal_title] = FTS.literals_for(title).join(' ').strip
|
541
|
+
to_search[:title] = title.join(' ').strip
|
542
|
+
|
543
|
+
to_search[:literal_keywords] = FTS.literals_for(keywords).join(' ').strip
|
544
|
+
to_search[:keywords] = keywords.join(' ').strip
|
545
|
+
|
546
|
+
to_search[:fulltext] = fulltext.join(' ').strip
|
547
|
+
|
548
|
+
#
|
436
549
|
to_search
|
437
550
|
end
|
438
551
|
end
|
439
552
|
|
440
|
-
def FTS.
|
441
|
-
|
553
|
+
def FTS.normalized_array(*array)
|
554
|
+
array.flatten.map{|_| _.to_s.strip}.select{|_| !_.empty?}
|
555
|
+
end
|
556
|
+
|
557
|
+
def FTS.literals_for(*words)
|
558
|
+
words = words.join(' ').strip.split(/\s+/)
|
559
|
+
|
560
|
+
words.map do |word|
|
561
|
+
next if word.empty?
|
562
|
+
if word =~ /\A__.*__\Z/
|
563
|
+
word
|
564
|
+
else
|
565
|
+
without_delimeters = word.to_s.scan(/[0-9a-zA-Z]/).join
|
566
|
+
literal = "__#{ without_delimeters }__"
|
567
|
+
literal
|
568
|
+
end
|
569
|
+
end.compact
|
570
|
+
end
|
571
|
+
|
572
|
+
def FTS.index(*args, &block)
|
573
|
+
if args.empty? and block.nil?
|
574
|
+
Index
|
575
|
+
else
|
576
|
+
Index.add(*args, &block)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
def FTS.unindex(*args, &block)
|
581
|
+
Index.remove(*args, &block)
|
582
|
+
end
|
583
|
+
|
584
|
+
def FTS.index!(*args, &block)
|
585
|
+
Index.add!(*args, &block)
|
586
|
+
end
|
587
|
+
|
588
|
+
def FTS.unindex!(*args, &block)
|
589
|
+
Index.remove!(*args, &block)
|
442
590
|
end
|
443
591
|
|
444
592
|
#
|
@@ -485,6 +633,11 @@ module Mongoid
|
|
485
633
|
super
|
486
634
|
ensure
|
487
635
|
other.module_eval(&Mixin.code)
|
636
|
+
|
637
|
+
FTS.models.dup.each do |model|
|
638
|
+
FTS.models.delete(model) if model.name == other.name
|
639
|
+
end
|
640
|
+
|
488
641
|
FTS.models.push(other)
|
489
642
|
FTS.models.uniq!
|
490
643
|
end
|
data/mongoid-fts.gemspec
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
Gem::Specification::new do |spec|
|
5
5
|
spec.name = "mongoid-fts"
|
6
|
-
spec.version = "0.
|
6
|
+
spec.version = "1.0.0"
|
7
7
|
spec.platform = Gem::Platform::RUBY
|
8
8
|
spec.summary = "mongoid-fts"
|
9
9
|
spec.description = "enable mongodb's new fulltext simply and quickly on your mongoid models, including pagination."
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-fts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ara T. Howard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-08-
|
11
|
+
date: 2013-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|