mongoid_search2 0.3.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +121 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/lib/mongoid_search/log.rb +22 -0
- data/lib/mongoid_search/mongoid_search.rb +127 -0
- data/lib/mongoid_search/railtie.rb +10 -0
- data/lib/mongoid_search/stemmers.rb +26 -0
- data/lib/mongoid_search/util.rb +45 -0
- data/lib/mongoid_search.rb +4 -0
- data/spec/config/ignorelist.yml +2 -0
- data/spec/coverage/assets/0.4.5/app.js +66 -0
- data/spec/coverage/assets/0.4.5/fancybox/blank.gif +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_close.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_loading.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_nav_left.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_nav_right.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_e.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_n.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_ne.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_nw.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_s.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_se.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_sw.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_shadow_w.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_title_left.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_title_main.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_title_over.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancy_title_right.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancybox-x.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancybox-y.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/fancybox.png +0 -0
- data/spec/coverage/assets/0.4.5/fancybox/jquery.fancybox-1.3.1.css +363 -0
- data/spec/coverage/assets/0.4.5/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
- data/spec/coverage/assets/0.4.5/favicon.png +0 -0
- data/spec/coverage/assets/0.4.5/jquery-1.4.2.min.js +155 -0
- data/spec/coverage/assets/0.4.5/jquery.dataTables.min.js +152 -0
- data/spec/coverage/assets/0.4.5/jquery.timeago.js +141 -0
- data/spec/coverage/assets/0.4.5/jquery.url.js +174 -0
- data/spec/coverage/assets/0.4.5/loading.gif +0 -0
- data/spec/coverage/assets/0.4.5/magnify.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/spec/coverage/assets/0.4.5/smoothness/jquery-ui-1.8.4.custom.css +295 -0
- data/spec/coverage/assets/0.4.5/stylesheet.css +341 -0
- data/spec/coverage/assets/0.5.3/app.js +88 -0
- data/spec/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
- data/spec/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
- data/spec/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
- data/spec/coverage/assets/0.5.3/favicon_green.png +0 -0
- data/spec/coverage/assets/0.5.3/favicon_red.png +0 -0
- data/spec/coverage/assets/0.5.3/favicon_yellow.png +0 -0
- data/spec/coverage/assets/0.5.3/highlight.css +129 -0
- data/spec/coverage/assets/0.5.3/highlight.pack.js +1 -0
- data/spec/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
- data/spec/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
- data/spec/coverage/assets/0.5.3/jquery.timeago.js +141 -0
- data/spec/coverage/assets/0.5.3/jquery.url.js +174 -0
- data/spec/coverage/assets/0.5.3/loading.gif +0 -0
- data/spec/coverage/assets/0.5.3/magnify.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/spec/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
- data/spec/coverage/assets/0.5.3/stylesheet.css +383 -0
- data/spec/coverage/index.html +702 -0
- data/spec/coverage/resultset.yml +631 -0
- data/spec/models/category.rb +6 -0
- data/spec/models/product.rb +13 -0
- data/spec/models/subproduct.rb +8 -0
- data/spec/models/tag.rb +6 -0
- data/spec/models/variant.rb +4 -0
- data/spec/mongoid_search_spec.rb +161 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/util_spec.rb +62 -0
- metadata +343 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Mauricio Zaffari
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
Mongoid Search
|
2
|
+
============
|
3
|
+
|
4
|
+
Mongoid Search is a simple full text search implementation for Mongoid ORM.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
--------
|
8
|
+
|
9
|
+
In your Gemfile:
|
10
|
+
|
11
|
+
gem 'mongoid_search'
|
12
|
+
|
13
|
+
Then:
|
14
|
+
|
15
|
+
bundle install
|
16
|
+
|
17
|
+
Examples
|
18
|
+
--------
|
19
|
+
|
20
|
+
class Product
|
21
|
+
include Mongoid::Document
|
22
|
+
include Mongoid::Search
|
23
|
+
field :brand
|
24
|
+
field :name
|
25
|
+
|
26
|
+
references_many :tags
|
27
|
+
refereced_in :category
|
28
|
+
|
29
|
+
search_in :brand, :name, :tags => :name, :category => :name
|
30
|
+
end
|
31
|
+
|
32
|
+
class Tag
|
33
|
+
include Mongoid::Document
|
34
|
+
field :name
|
35
|
+
|
36
|
+
referenced_in :product
|
37
|
+
end
|
38
|
+
|
39
|
+
class Category
|
40
|
+
include Mongoid::Document
|
41
|
+
field :name
|
42
|
+
|
43
|
+
references_many :products
|
44
|
+
end
|
45
|
+
|
46
|
+
Now when you save a product, you get a _keywords field automatically:
|
47
|
+
|
48
|
+
p = Product.new :brand => "Apple", :name => "iPhone"
|
49
|
+
p.tags << Tag.new(:name => "Amazing")
|
50
|
+
p.tags << Tag.new(:name => "Awesome")
|
51
|
+
p.tags << Tag.new(:name => "Superb")
|
52
|
+
p.save
|
53
|
+
=> true
|
54
|
+
p._keywords
|
55
|
+
|
56
|
+
Now you can run search, which will look in the _keywords field and return all matching results:
|
57
|
+
|
58
|
+
Product.search("apple iphone").size
|
59
|
+
=> 1
|
60
|
+
|
61
|
+
Note that the search is case insensitive, and accept partial searching too:
|
62
|
+
|
63
|
+
Product.search("ipho").size
|
64
|
+
=> 1
|
65
|
+
|
66
|
+
Assuming you have a category with multiple products you can now use the following
|
67
|
+
code to search for 'iphone' in products cheaper than $499
|
68
|
+
|
69
|
+
@category.products.where(:price.lt => 499).csearch('iphone').asc(:price)
|
70
|
+
|
71
|
+
In this case we have to use csearch, an alias for search, because since v2.0.0
|
72
|
+
Mongoid defines it's own Criteria.search method.
|
73
|
+
|
74
|
+
|
75
|
+
Options
|
76
|
+
-------
|
77
|
+
|
78
|
+
match:
|
79
|
+
_:any_ - match any occurrence
|
80
|
+
_:all_ - match all ocurrences
|
81
|
+
Default is _:any_.
|
82
|
+
|
83
|
+
search_in :brand, :name, { :tags => :name }, { :match => :any }
|
84
|
+
|
85
|
+
Product.search("apple motorola").size
|
86
|
+
=> 1
|
87
|
+
|
88
|
+
search_in :brand, :name, { :tags => :name }, { :match => :all }
|
89
|
+
|
90
|
+
Product.search("apple motorola").size
|
91
|
+
=> 0
|
92
|
+
|
93
|
+
allow_empty_search:
|
94
|
+
_true_ - match any occurrence
|
95
|
+
_false_ - match all ocurrences
|
96
|
+
Default is _false_.
|
97
|
+
|
98
|
+
search_in :brand, :name, { :tags => :name }, { :allow_empty_search => true }
|
99
|
+
|
100
|
+
Product.search("").size
|
101
|
+
=> 1
|
102
|
+
|
103
|
+
ignore_list:
|
104
|
+
Pass in an ignore list location. Keywords in that list will be ignored.
|
105
|
+
|
106
|
+
search_in :brand, :name, { :tags => :name }, { :ignore_list => Rails.root.join("config", "ignorelist.yml") }
|
107
|
+
|
108
|
+
The list should look like:
|
109
|
+
|
110
|
+
ignorelist:
|
111
|
+
a, an, to, from, as
|
112
|
+
|
113
|
+
You can include how many keywords you like.
|
114
|
+
|
115
|
+
TODO
|
116
|
+
----
|
117
|
+
|
118
|
+
* Strip html with sanitize (https://github.com/rgrove/sanitize)
|
119
|
+
* Rewrite and test relevant search
|
120
|
+
* Move all configurations to a configuration file. Maybe /config/mongoid_search.yml.
|
121
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
8
|
+
spec.rspec_opts = ["-c", "-f progress"]
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.2
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MongoidSearch
|
2
|
+
class Log
|
3
|
+
cattr_accessor :silent
|
4
|
+
|
5
|
+
def self.log(message)
|
6
|
+
print message unless silent
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.red(text)
|
10
|
+
log(colorize(text, 31))
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.green(text)
|
14
|
+
log(colorize(text, 32))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def self.colorize(text, code)
|
19
|
+
"\033[#{code}m#{text}\033[0m"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "mongoid_search/stemmers"
|
2
|
+
|
3
|
+
module Mongoid::Search
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
cattr_accessor :search_fields, :match, :allow_empty_search, :relevant_search, :stem_keywords, :stemmer_class, :ignore_list
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
@classes ||= []
|
12
|
+
@classes << base
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.classes
|
16
|
+
@classes
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods #:nodoc:
|
20
|
+
# Set a field or a number of fields as sources for search
|
21
|
+
def search_in(*args)
|
22
|
+
options = args.last.is_a?(Hash) && [:match, :allow_empty_search, :relevant_search, :stem_keywords, :ignore_list].include?(args.last.keys.first) ? args.pop : {}
|
23
|
+
self.match = [:any, :all].include?(options[:match]) ? options[:match] : :any
|
24
|
+
self.allow_empty_search = [true, false].include?(options[:allow_empty_search]) ? options[:allow_empty_search] : false
|
25
|
+
self.relevant_search = [true, false].include?(options[:relevant_search]) ? options[:relevant_search] : false
|
26
|
+
self.stemmer_class = options[:stemmer_class]
|
27
|
+
self.stem_keywords = !!stemmer_class || options[:stem_keywords]
|
28
|
+
self.stemmer_class ||= MongoidSearch::FastStemmer
|
29
|
+
self.ignore_list = YAML.load(File.open(options[:ignore_list]))["ignorelist"] if options[:ignore_list].present?
|
30
|
+
self.search_fields = (self.search_fields || []).concat args
|
31
|
+
|
32
|
+
field :_keywords, :type => Array
|
33
|
+
index :_keywords, :background => true
|
34
|
+
|
35
|
+
before_save :set_keywords
|
36
|
+
end
|
37
|
+
|
38
|
+
def search(query, options={})
|
39
|
+
if relevant_search
|
40
|
+
search_relevant(query, options)
|
41
|
+
else
|
42
|
+
search_without_relevance(query, options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Mongoid 2.0.0 introduces Criteria.seach so we need to provide
|
47
|
+
# alternate method
|
48
|
+
alias csearch search
|
49
|
+
|
50
|
+
def search_without_relevance(query, options={})
|
51
|
+
return criteria.all if query.blank? && allow_empty_search
|
52
|
+
criteria.send("#{(options[:match]||self.match).to_s}_in", :_keywords => MongoidSearch::Util.normalize_keywords(query, keyword_stemmer(options), ignore_list).map { |q| /#{q}/ })
|
53
|
+
end
|
54
|
+
|
55
|
+
def keyword_stemmer(options={})
|
56
|
+
stemmer_class.new(:language => options[:language])
|
57
|
+
end
|
58
|
+
|
59
|
+
def search_relevant(query, options={})
|
60
|
+
return criteria.all if query.blank? && allow_empty_search
|
61
|
+
|
62
|
+
keywords = MongoidSearch::Util.normalize_keywords(query, keyword_stemmer(options), ignore_list)
|
63
|
+
|
64
|
+
map = <<-EOS
|
65
|
+
function() {
|
66
|
+
var entries = 0
|
67
|
+
for(i in keywords)
|
68
|
+
for(j in this._keywords) {
|
69
|
+
if(this._keywords[j] == keywords[i])
|
70
|
+
entries++
|
71
|
+
}
|
72
|
+
if(entries > 0)
|
73
|
+
emit(this._id, entries)
|
74
|
+
}
|
75
|
+
EOS
|
76
|
+
reduce = <<-EOS
|
77
|
+
function(key, values) {
|
78
|
+
return(values[0])
|
79
|
+
}
|
80
|
+
EOS
|
81
|
+
|
82
|
+
#raise [self.class, self.inspect].inspect
|
83
|
+
|
84
|
+
kw_conditions = keywords.map do |kw|
|
85
|
+
{:_keywords => kw}
|
86
|
+
end
|
87
|
+
|
88
|
+
criteria = (criteria || self).any_of(*kw_conditions)
|
89
|
+
|
90
|
+
query = criteria.selector
|
91
|
+
|
92
|
+
options.delete(:limit)
|
93
|
+
options.delete(:skip)
|
94
|
+
options.merge! :scope => {:keywords => keywords}, :query => query
|
95
|
+
|
96
|
+
# res = collection.map_reduce(map, reduce, options)
|
97
|
+
# res.find.sort(['value', -1]) # Cursor
|
98
|
+
collection.map_reduce(map, reduce, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Goes through all documents in the class that includes Mongoid::Search
|
102
|
+
# and indexes the keywords.
|
103
|
+
def index_keywords!
|
104
|
+
all.each { |d| d.index_keywords! ? MongoidSearch::Log.green(".") : MongoidSearch::Log.red("F") }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Indexes the document keywords
|
109
|
+
def index_keywords!
|
110
|
+
update_attribute(:_keywords, set_keywords)
|
111
|
+
end
|
112
|
+
|
113
|
+
def keyword_language
|
114
|
+
super if defined?(super)
|
115
|
+
end
|
116
|
+
|
117
|
+
def keyword_stemmer
|
118
|
+
self.class.stemmer_class.new(:language => keyword_language) if stem_keywords
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def set_keywords
|
123
|
+
self._keywords = self.search_fields.map do |field|
|
124
|
+
MongoidSearch::Util.keywords(self, field, keyword_stemmer, ignore_list)
|
125
|
+
end.flatten.reject{|k| k.nil? || k.empty?}.uniq.sort
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MongoidSearch
|
2
|
+
class FastStemmer
|
3
|
+
def initialize(*args)
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(word)
|
7
|
+
::Stemmer.stem_word(word)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class LinguaStemmer
|
12
|
+
def initialize(*args, &block)
|
13
|
+
@stemmer = ::Lingua::Stemmer.new(*args, &block)
|
14
|
+
rescue Lingua::StemmerError => e
|
15
|
+
raise unless e.message.include?("not available")
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(word)
|
19
|
+
if @stemmer
|
20
|
+
@stemmer.stem(word)
|
21
|
+
else
|
22
|
+
word
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MongoidSearch
|
2
|
+
module Util
|
3
|
+
|
4
|
+
def self.keywords(klass, field, stemmer, ignore_list)
|
5
|
+
if field.is_a?(Hash)
|
6
|
+
field.keys.map do |key|
|
7
|
+
attribute = klass.send(key)
|
8
|
+
unless attribute.blank?
|
9
|
+
method = field[key]
|
10
|
+
if attribute.is_a?(Array)
|
11
|
+
if method.is_a?(Array)
|
12
|
+
method.map {|m| attribute.map { |a| Util.normalize_keywords a.send(m), stemmer, ignore_list } }
|
13
|
+
else
|
14
|
+
attribute.map(&method).map { |t| Util.normalize_keywords t, stemmer, ignore_list }
|
15
|
+
end
|
16
|
+
else
|
17
|
+
Util.normalize_keywords(attribute.send(method), stemmer, ignore_list)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
value = klass[field]
|
23
|
+
value = value.join(' ') if value.respond_to?(:join)
|
24
|
+
Util.normalize_keywords(value, stemmer, ignore_list) if value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.normalize_keywords(text, stemmer, ignore_list)
|
29
|
+
return [] if text.blank?
|
30
|
+
text = text.to_s.
|
31
|
+
mb_chars.
|
32
|
+
normalize(:kd).
|
33
|
+
downcase.
|
34
|
+
to_s.
|
35
|
+
gsub(/[._:;'"`,?|+={}()!@#%^&*<>~\$\-\\\/\[\]]/, ' '). # strip punctuation
|
36
|
+
gsub(/[^[:alnum:]\s]/,''). # strip accents
|
37
|
+
split(' ').
|
38
|
+
reject { |word| word.size < 2 }
|
39
|
+
text = text.reject { |word| ignore_list.include?(word) } unless ignore_list.blank?
|
40
|
+
text = text.map{ |word| stemmer.call(word) } if stemmer
|
41
|
+
text
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
$(document).ready(function() {
|
2
|
+
$('.file_list').dataTable({
|
3
|
+
"aaSorting": [[ 1, "asc" ]],
|
4
|
+
"bPaginate": false,
|
5
|
+
"bJQueryUI": true,
|
6
|
+
"aoColumns": [
|
7
|
+
null,
|
8
|
+
{ "sType": "percent" },
|
9
|
+
null,
|
10
|
+
null,
|
11
|
+
null,
|
12
|
+
null
|
13
|
+
]
|
14
|
+
});
|
15
|
+
|
16
|
+
$('.source_table tbody tr:odd').addClass('odd');
|
17
|
+
$('.source_table tbody tr:even').addClass('even');
|
18
|
+
|
19
|
+
$("a.src_link").fancybox({
|
20
|
+
'hideOnContentClick': true
|
21
|
+
});
|
22
|
+
|
23
|
+
// Hide src files and file list container
|
24
|
+
$('.source_files').hide();
|
25
|
+
$('.file_list_container').hide();
|
26
|
+
|
27
|
+
// Add tabs based upon existing file_list_containers
|
28
|
+
$('.file_list_container h2').each(function(){
|
29
|
+
$('.group_tabs').append('<li><a href="#' + $(this).parent().attr('id') + '">' + $(this).html() + '</a></li>');
|
30
|
+
});
|
31
|
+
|
32
|
+
$('.group_tabs a').each( function() {
|
33
|
+
$(this).addClass($(this).attr('href').replace('#', ''));
|
34
|
+
});
|
35
|
+
|
36
|
+
$('.group_tabs a').live('focus', function() {
|
37
|
+
$(this).blur();
|
38
|
+
});
|
39
|
+
|
40
|
+
var favicon_path = $('link[rel="shortcut icon"]').attr('href')
|
41
|
+
$('.group_tabs a').live('click', function(){
|
42
|
+
if (!$(this).parent().hasClass('active')) {
|
43
|
+
$('.group_tabs a').parent().removeClass('active');
|
44
|
+
$(this).parent().addClass('active');
|
45
|
+
$('.file_list_container').hide();
|
46
|
+
$(".file_list_container" + $(this).attr('href')).show();
|
47
|
+
window.location.href = window.location.href.split('#')[0] + $(this).attr('href').replace('#', '#_');
|
48
|
+
|
49
|
+
// Force favicon reload - otherwise the location change containing anchor would drop the favicon...
|
50
|
+
// Works only on firefox, but still... - Anyone know a better solution to force favicon?
|
51
|
+
$('link[rel="shortcut icon"]').remove();
|
52
|
+
$('head').append('<link rel="shortcut icon" type="image/png" href="'+ favicon_path +'" />');
|
53
|
+
};
|
54
|
+
return false;
|
55
|
+
});
|
56
|
+
|
57
|
+
if (jQuery.url.attr('anchor')) {
|
58
|
+
$('.group_tabs a.'+jQuery.url.attr('anchor').replace('_', '')).click();
|
59
|
+
} else {
|
60
|
+
$('.group_tabs a:first').click();
|
61
|
+
};
|
62
|
+
|
63
|
+
$("abbr.timeago").timeago();
|
64
|
+
$('#loading').fadeOut();
|
65
|
+
$('#wrapper').show();
|
66
|
+
});
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|