smart_search 0.0.65 → 0.0.67
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/lib/smart_search.rb +49 -19
- data/lib/smart_search/smart_search_engine.rb +2 -0
- data/lib/smart_search_ignore_word.rb +2 -0
- data/lib/smart_search_tag.rb +1 -0
- data/lib/smart_similarity.rb +12 -6
- data/test/unit/01_smart_search_test.rb +13 -7
- data/test/unit/03_smart_search_boost_test.rb +60 -0
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea2edfd305ac9960e74f36148bc8710e0b877416
|
4
|
+
data.tar.gz: 0d7d5bd0c94c3624d11d83fded3abcd6cb1a42a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b5192daa702b12f37fbd75dda8a077815ffd63974e9f8fd0c4a95c29adfa35f942e413c677f7c9d6e631c20cc5e617716b1267c3d5a06fde21c1dcc37cfc9e9
|
7
|
+
data.tar.gz: 94667a98ba392b0e0dafe5339f425e27cc4d0d5fbbd75450e815a0271a25a700e6c3c9d282b896bf49033bad72d34c0bb3053c9729ab0e8adf7782caeb5001a1
|
data/lib/smart_search.rb
CHANGED
@@ -15,6 +15,7 @@ module SmartSearch
|
|
15
15
|
base.extend ClassMethods
|
16
16
|
end
|
17
17
|
|
18
|
+
# Class Methods for ActiveRecord
|
18
19
|
module ClassMethods
|
19
20
|
# Enable SmartSearch for the current ActiveRecord model.
|
20
21
|
# accepts options:
|
@@ -26,14 +27,16 @@ module SmartSearch
|
|
26
27
|
def smart_search(options = {:on => [], :conditions => nil, :group => nil, :order => "created_at", :force => false})
|
27
28
|
if table_exists?
|
28
29
|
# Check if search_tags exists
|
29
|
-
if !is_smart_search? || options[:force] == true
|
30
|
+
if !is_smart_search? || options[:force] == true || Rails.env == "test"
|
30
31
|
|
31
|
-
cattr_accessor :condition_default, :group_default, :tags, :order_default, :enable_similarity
|
32
|
+
cattr_accessor :condition_default, :group_default, :tags, :order_default, :enable_similarity, :default_template_path
|
32
33
|
send :include, InstanceMethods
|
33
34
|
self.send(:after_save, :create_search_tags)
|
34
35
|
self.send(:before_destroy, :create_search_tags)
|
35
36
|
self.enable_similarity ||= true
|
36
37
|
|
38
|
+
attr_accessor :query_score
|
39
|
+
|
37
40
|
# options zuweisen
|
38
41
|
if options[:conditions].is_a?(String) && !options[:conditions].blank?
|
39
42
|
self.condition_default = options[:conditions]
|
@@ -43,11 +46,7 @@ module SmartSearch
|
|
43
46
|
self.condition_default = nil
|
44
47
|
end
|
45
48
|
|
46
|
-
|
47
|
-
self.order_default = options[:order] || "created_at"
|
48
|
-
else
|
49
|
-
self.order_default = options[:order] || "id"
|
50
|
-
end
|
49
|
+
self.order_default = options[:order]
|
51
50
|
|
52
51
|
self.tags = options[:on] || []
|
53
52
|
end
|
@@ -73,7 +72,7 @@ module SmartSearch
|
|
73
72
|
self.connection.execute("INSERT INTO `#{::SmartSearchHistory.table_name}` (`query`) VALUES ('#{tags.gsub(/[^a-zA-ZäöüÖÄÜß\ ]/, '')}');")
|
74
73
|
end
|
75
74
|
|
76
|
-
tags = tags.split(
|
75
|
+
tags = tags.split(/[\ -]/).select {|t| !t.blank?}
|
77
76
|
|
78
77
|
# Fallback for Empty String
|
79
78
|
tags << "#" if tags.empty?
|
@@ -90,12 +89,24 @@ module SmartSearch
|
|
90
89
|
end
|
91
90
|
|
92
91
|
# Load ranking from Search tags
|
93
|
-
result_ids =
|
92
|
+
result_ids = []
|
93
|
+
result_scores = {}
|
94
|
+
SmartSearchTag.connection.select_all("select entry_id, sum(boost) as score, group_concat(search_tags) as grouped_tags
|
94
95
|
from smart_search_tags where `table_name`= '#{self.table_name}' and
|
95
96
|
|
96
|
-
(#{tags.join(' OR ')}) group by entry_id having (#{tags.join(' AND ').gsub('search_tags', 'grouped_tags')}) order by
|
97
|
+
(#{tags.join(' OR ')}) group by entry_id having (#{tags.join(' AND ').gsub('search_tags', 'grouped_tags')}) order by score DESC").each do |r|
|
98
|
+
result_ids << r["entry_id"].to_i
|
99
|
+
result_scores[r["entry_id"].to_i] = r['score'].to_f
|
100
|
+
end
|
101
|
+
|
102
|
+
# Enable unscoped searching
|
103
|
+
if options[:unscoped] == true
|
104
|
+
results = self.unscoped.where(:id => result_ids)
|
105
|
+
else
|
106
|
+
results = self.where(:id => result_ids)
|
107
|
+
end
|
108
|
+
|
97
109
|
|
98
|
-
results = self.where(:id => result_ids)
|
99
110
|
|
100
111
|
if options[:conditions]
|
101
112
|
results = results.where(options[:conditions])
|
@@ -105,15 +116,21 @@ module SmartSearch
|
|
105
116
|
results = results.where(self.condition_default)
|
106
117
|
end
|
107
118
|
|
108
|
-
if options[:group]
|
119
|
+
if options[:group]
|
109
120
|
results = results.group(options[:group])
|
110
121
|
end
|
111
122
|
|
112
|
-
if options[:order]
|
113
|
-
results = results.order(options[:order])
|
123
|
+
if options[:order] || self.order_default
|
124
|
+
results = results.order(options[:order] || self.order_default)
|
114
125
|
else
|
115
|
-
|
116
|
-
|
126
|
+
ordered_results = []
|
127
|
+
results.each do |r|
|
128
|
+
r.query_score = result_scores[r.id]
|
129
|
+
ordered_results[result_ids.index(r.id)] = r
|
130
|
+
end
|
131
|
+
|
132
|
+
results = ordered_results.compact
|
133
|
+
end
|
117
134
|
|
118
135
|
return results
|
119
136
|
else
|
@@ -125,7 +142,7 @@ module SmartSearch
|
|
125
142
|
def set_search_index
|
126
143
|
s = self.all.size.to_f
|
127
144
|
self.all.each_with_index do |a, i|
|
128
|
-
a.create_search_tags
|
145
|
+
a.create_search_tags
|
129
146
|
a.send(:update_without_callbacks)
|
130
147
|
done = ((i+1).to_f/s)*100
|
131
148
|
printf "Set search index for #{self.name}: #{done}%% \r"
|
@@ -142,8 +159,10 @@ module SmartSearch
|
|
142
159
|
|
143
160
|
end
|
144
161
|
|
162
|
+
# Instance Methods for ActiveRecord
|
145
163
|
module InstanceMethods
|
146
164
|
|
165
|
+
# Load the result template path for this instance
|
147
166
|
def result_template_path
|
148
167
|
self.class.result_template_path
|
149
168
|
end
|
@@ -178,14 +197,25 @@ module SmartSearch
|
|
178
197
|
|
179
198
|
|
180
199
|
self.clear_search_tags
|
181
|
-
|
200
|
+
|
201
|
+
# Merge search tags with same boost
|
202
|
+
#merged_tags = {}
|
203
|
+
#tags.each do |t|
|
204
|
+
# if merged_tags[t[:boost]]
|
205
|
+
# merged_tags[t[:boost]][:field_name] << ",#{t[:field_name].to_s}"
|
206
|
+
# merged_tags[t[:boost]][:search_tags] << " #{t[:search_tags]}"
|
207
|
+
# else
|
208
|
+
# merged_tags[t[:boost]] = {:field_name => t[:field_name].to_s, :search_tags => t[:search_tags], :boost => t[:boost] }
|
209
|
+
# end
|
210
|
+
#end
|
211
|
+
|
182
212
|
tags.each do |t|
|
183
213
|
SmartSearchTag.create(t.merge!(:table_name => self.class.table_name, :entry_id => self.id))
|
184
214
|
end
|
185
|
-
|
186
215
|
|
187
216
|
end
|
188
217
|
|
218
|
+
# Remove search data for the instance from the index
|
189
219
|
def clear_search_tags
|
190
220
|
if !self.id.nil?
|
191
221
|
SmartSearchTag.connection.execute("DELETE from #{SmartSearchTag.table_name} where `table_name` = '#{self.class.table_name}' and entry_id = #{self.id}")
|
data/lib/smart_search_tag.rb
CHANGED
data/lib/smart_similarity.rb
CHANGED
@@ -16,7 +16,7 @@ class SmartSimilarity < ActiveRecord::Base
|
|
16
16
|
|
17
17
|
#== Konstanten
|
18
18
|
# Defines the min. result of word simililarity check
|
19
|
-
SIMILARITY_FACTOR = 0.
|
19
|
+
SIMILARITY_FACTOR = 0.77
|
20
20
|
# Defines first simililarity check method
|
21
21
|
SIMILARITY_METHOD_1 = :jarowinkler
|
22
22
|
# Defines first simililarity check method
|
@@ -24,9 +24,10 @@ class SmartSimilarity < ActiveRecord::Base
|
|
24
24
|
|
25
25
|
# An average of both results will generated and compered with 'SIMILARITY_FACTOR'
|
26
26
|
|
27
|
-
# Limit Number of similar words
|
27
|
+
# Limit Number of similar words (still unused)
|
28
28
|
SIMILARITY_LIMIT = 8
|
29
29
|
|
30
|
+
# USe this regexp to split texts into words
|
30
31
|
SPLITTING_REGEXP = /\b/
|
31
32
|
|
32
33
|
#== Validation and Callbacks
|
@@ -52,8 +53,12 @@ class SmartSimilarity < ActiveRecord::Base
|
|
52
53
|
else
|
53
54
|
current = words_in_db.similarities
|
54
55
|
end
|
55
|
-
|
56
|
-
|
56
|
+
|
57
|
+
# If word is a substring of similarity word, it must not be saved,
|
58
|
+
# cause it will match anyway:
|
59
|
+
# 'how' will match 'show', so 'show' is not needed in index for 'how'
|
60
|
+
# Vice Versa, 'how' should also be found if query is 'show', so it will be kept in the index
|
61
|
+
current += prepared_text.select {|w| w != word && self.match_words(w,word) >= SIMILARITY_FACTOR && !w.match(word)}
|
57
62
|
|
58
63
|
list[word] = current.uniq
|
59
64
|
end
|
@@ -101,8 +106,9 @@ class SmartSimilarity < ActiveRecord::Base
|
|
101
106
|
|
102
107
|
# Loads your created query history and saves them to the index
|
103
108
|
def self.load_from_query_history
|
104
|
-
queries = ActiveRecord::Base.connection.select_all("SELECT query from `#{::SmartSearchHistory.table_name}`").map {|r| r["query"]}
|
105
|
-
self.
|
109
|
+
queries = ActiveRecord::Base.connection.select_all("SELECT query from `#{::SmartSearchHistory.table_name}`").map {|r| r["query"]}
|
110
|
+
queries.each {|q| self.add_word(q) }
|
111
|
+
|
106
112
|
self.connection.execute("TRUNCATE `#{::SmartSearchHistory.table_name}`")
|
107
113
|
end
|
108
114
|
|
@@ -61,15 +61,21 @@ class SmartSearchTest < Test::Unit::TestCase
|
|
61
61
|
|
62
62
|
assert_equal user_c, User.find_by_tags("test", :order => :last_name).first
|
63
63
|
assert_equal user_a, User.find_by_tags("test", :order => :last_name).last
|
64
|
-
end
|
64
|
+
end
|
65
65
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
def test_search_tags_should_work_with_array_of_strings
|
67
|
+
User.smart_search :on => %w(first_name last_name office.name), :force => true
|
68
|
+
o = Office.create(:name => "Neandertal")
|
69
|
+
u = User.create(:first_name => "Homo", :last_name => "Sapiens", :office_id => o.id)
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def test_should_create_search_history
|
75
|
+
User.find_by_tags("XXXYYY")
|
76
|
+
|
77
|
+
assert_not_equal 0, SmartSearchHistory.where(:query => "XXXYYY").size
|
78
|
+
end
|
73
79
|
|
74
80
|
|
75
81
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "test_helper"
|
3
|
+
class SmartSearchBoostTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_boost_search_index_cols_should_be_created
|
6
|
+
SmartSearchTag.connection.execute("TRUNCATE #{SmartSearchTag.table_name}")
|
7
|
+
Customer.smart_search :on => [
|
8
|
+
{:field_name => :first_name, :boost => 1},
|
9
|
+
{:field_name => :last_name, :boost => 2},
|
10
|
+
{:field_name => "user.full_name", :boost => 0.5},
|
11
|
+
], :force => true
|
12
|
+
|
13
|
+
user = User.create(:first_name => "Pi", :last_name => "Pa")
|
14
|
+
|
15
|
+
Customer.create(:first_name => "Lorem", :last_name => "Ipsum", :user_id => user.id)
|
16
|
+
|
17
|
+
assert_equal 1, SmartSearchTag.where(:field_name => "first_name", :boost => 1).count
|
18
|
+
assert_equal 1, SmartSearchTag.where(:field_name => "last_name", :boost => 2).count
|
19
|
+
assert_equal 1, SmartSearchTag.where(:field_name => "user.full_name", :boost => 0.5).count
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_boost_search__results_should_order_by_score
|
23
|
+
Customer.smart_search :on => [
|
24
|
+
{:field_name => :first_name, :boost => 1},
|
25
|
+
{:field_name => :last_name, :boost => 2},
|
26
|
+
{:field_name => "user.full_name", :boost => 0.5},
|
27
|
+
], :force => true
|
28
|
+
|
29
|
+
user = User.create(:first_name => "Rudi", :last_name => "Piff")
|
30
|
+
|
31
|
+
c1 = Customer.create(:first_name => "Rudi", :last_name => "Rolle", :user_id => user.id)
|
32
|
+
c2 = Customer.create(:first_name => "Rolle", :last_name => "Rudi", :user_id => user.id)
|
33
|
+
c3 = Customer.create(:first_name => "Jackie", :last_name => "Brown", :user_id => user.id)
|
34
|
+
|
35
|
+
results = Customer.find_by_tags("Rudi")
|
36
|
+
|
37
|
+
assert_equal c1, results[1]
|
38
|
+
assert_equal c2, results[0]
|
39
|
+
assert_equal c3, results[2]
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def test_same_boost_search_index_cols_should_be_grouped
|
44
|
+
|
45
|
+
Customer.smart_search :on => [
|
46
|
+
{:field_name => :first_name, :boost => 2},
|
47
|
+
{:field_name => :last_name, :boost => 2},
|
48
|
+
{:field_name => "user.full_name", :boost => 0.5},
|
49
|
+
], :force => true
|
50
|
+
|
51
|
+
user = User.create(:first_name => "Pipi", :last_name => "Papa")
|
52
|
+
|
53
|
+
customer = Customer.create(:first_name => "Lorem", :last_name => "Ipsum", :user_id => user.id)
|
54
|
+
|
55
|
+
assert_equal 2, SmartSearchTag.where(:table_name => Customer.table_name, :entry_id => customer.id).count
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.67
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Eck
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 3.2.9
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 3.2.9
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -66,8 +66,9 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
description: SmartSearch adds full-text search functions to ActiveRecord
|
70
|
-
search for similiar words. Its fast, simple, and works with almost
|
69
|
+
description: SmartSearch adds full-text search functions to ActiveRecord running with
|
70
|
+
MySQL, including search for similiar words. Its fast, simple, and works with almost
|
71
|
+
zero-config!
|
71
72
|
email: it-support@friends-systems.de
|
72
73
|
executables: []
|
73
74
|
extensions: []
|
@@ -88,7 +89,8 @@ files:
|
|
88
89
|
- test/test_helper.rb
|
89
90
|
- test/unit/01_smart_search_test.rb
|
90
91
|
- test/unit/02_smart_search_similarity_test.rb
|
91
|
-
|
92
|
+
- test/unit/03_smart_search_boost_test.rb
|
93
|
+
homepage: https://github.com/florianeck/smart_search
|
92
94
|
licenses: []
|
93
95
|
metadata: {}
|
94
96
|
post_install_message:
|
@@ -117,3 +119,5 @@ test_files:
|
|
117
119
|
- test/test_helper.rb
|
118
120
|
- test/unit/01_smart_search_test.rb
|
119
121
|
- test/unit/02_smart_search_similarity_test.rb
|
122
|
+
- test/unit/03_smart_search_boost_test.rb
|
123
|
+
has_rdoc:
|