smart_search 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/smart_search.rb +112 -106
- data/lib/tasks/smart_search.rake +22 -22
- metadata +10 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 869d08c2cff4be98569eb734dfdce5d675807fc2
|
4
|
+
data.tar.gz: bc6139fd0e37f7c0fc5c9d74d056d91a5132d6e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d195f3d1ab9c0a5a7317ffc5412b4708ad440aa10e05079e5729ee3865866f79f0ddd820634ed189920811e859f6f72b648aa655a4d83ff261885a72872772f
|
7
|
+
data.tar.gz: 31f672e5448d8b491016c132f34b7288b8e057b8b98e039466c83a89d564b99c0c7781ece2812005c4e50dbff6871aa19facf20c0a378bda0ea26023f04a5cb1
|
data/lib/smart_search.rb
CHANGED
@@ -10,11 +10,11 @@ require "smart_search_tag"
|
|
10
10
|
|
11
11
|
|
12
12
|
module SmartSearch
|
13
|
-
|
13
|
+
|
14
14
|
def self.included(base)
|
15
15
|
base.extend ClassMethods
|
16
|
-
end
|
17
|
-
|
16
|
+
end
|
17
|
+
|
18
18
|
# Class Methods for ActiveRecord
|
19
19
|
module ClassMethods
|
20
20
|
# Enable SmartSearch for the current ActiveRecord model.
|
@@ -28,228 +28,234 @@ module SmartSearch
|
|
28
28
|
if table_exists?
|
29
29
|
# Check if search_tags exists
|
30
30
|
if !is_smart_search? || options[:force] == true || Rails.env == "test"
|
31
|
-
|
31
|
+
|
32
32
|
cattr_accessor :condition_default, :group_default, :tags, :order_default, :enable_similarity, :default_template_path
|
33
33
|
send :include, InstanceMethods
|
34
|
-
self.send(:after_save, :create_search_tags)
|
34
|
+
self.send(:after_save, :create_search_tags, :if => :update_search_tags?) unless options[:auto] == false
|
35
35
|
self.send(:before_destroy, :clear_search_tags)
|
36
36
|
self.enable_similarity ||= true
|
37
|
-
|
38
|
-
attr_accessor :query_score
|
39
|
-
|
37
|
+
|
38
|
+
attr_accessor :query_score, :dont_update_search_tags
|
39
|
+
|
40
40
|
# options zuweisen
|
41
41
|
if options[:conditions].is_a?(String) && !options[:conditions].blank?
|
42
42
|
self.condition_default = options[:conditions]
|
43
43
|
elsif !options[:conditions].nil?
|
44
|
-
raise ArgumentError, ":conditions must be a valid SQL Query"
|
44
|
+
raise ArgumentError, ":conditions must be a valid SQL Query"
|
45
45
|
else
|
46
46
|
self.condition_default = nil
|
47
|
-
end
|
48
|
-
|
47
|
+
end
|
48
|
+
|
49
49
|
self.order_default = options[:order]
|
50
50
|
|
51
51
|
self.tags = options[:on] || []
|
52
52
|
end
|
53
|
-
end
|
53
|
+
end
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
# Verify if SmartSearch already loaded for this model
|
57
57
|
def is_smart_search?
|
58
58
|
self.included_modules.include?(InstanceMethods)
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
# defines where to look for a partial to load when displaying results for this model
|
62
62
|
def result_template_path
|
63
63
|
"/search/results/#{self.name.split("::").last.underscore}"
|
64
|
-
end
|
65
|
-
|
64
|
+
end
|
65
|
+
|
66
66
|
# Serach database for given search tags
|
67
67
|
def find_by_tags(tags = "", options = {})
|
68
68
|
if self.is_smart_search?
|
69
|
-
|
69
|
+
|
70
|
+
tags = tags.join(" ") if tags.is_a?(Array)
|
71
|
+
|
70
72
|
# Save Data for similarity analysis
|
71
73
|
if tags.size > 3
|
72
74
|
self.connection.execute("INSERT INTO `#{::SmartSearchHistory.table_name}` (`query`) VALUES ('#{tags.gsub(/[^a-zA-ZäöüÖÄÜß\ ]/, '')}');")
|
73
|
-
end
|
74
|
-
|
75
|
-
tags = tags.split(/[\ -]/).select {|t| !t.blank?}
|
76
|
-
|
75
|
+
end
|
76
|
+
|
77
|
+
tags = tags.gsub(/[\(\)\[\]\'\"\*\%\|]/, '').split(/[\ -]/).select {|t| !t.blank?}
|
78
|
+
|
77
79
|
# Fallback for Empty String
|
78
80
|
tags << "#" if tags.empty?
|
79
|
-
|
81
|
+
|
80
82
|
# Similarity
|
81
83
|
if self.enable_similarity == true
|
82
|
-
tags.map! do |t|
|
84
|
+
tags.map! do |t|
|
83
85
|
similars = SmartSimilarity.similars(t, :increment_counter => true).join("|")
|
84
86
|
"search_tags REGEXP '#{similars}'"
|
85
|
-
end
|
86
|
-
|
87
|
+
end
|
88
|
+
|
87
89
|
else
|
88
90
|
tags.map! {|t| "search_tags LIKE '%#{t}%'"}
|
89
|
-
end
|
90
|
-
|
91
|
+
end
|
92
|
+
|
91
93
|
# Load ranking from Search tags
|
92
94
|
result_ids = []
|
93
95
|
result_scores = {}
|
94
|
-
SmartSearchTag.connection.select_all("select entry_id, sum(boost) as score, group_concat(search_tags) as grouped_tags
|
95
|
-
from smart_search_tags where `table_name`= '#{self.table_name}' and
|
96
|
-
|
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
|
96
|
+
SmartSearchTag.connection.select_all("select entry_id, sum(boost) as score, group_concat(search_tags) as grouped_tags
|
97
|
+
from smart_search_tags where `table_name`= '#{self.table_name}' and
|
98
|
+
|
99
|
+
(#{tags.join(' OR ')}) group by entry_id having (#{tags.join(' AND ').gsub('search_tags', 'grouped_tags')}) order by score DESC").each do |r|
|
100
|
+
result_ids << r["entry_id"].to_i
|
99
101
|
result_scores[r["entry_id"].to_i] = r['score'].to_f
|
100
|
-
end
|
101
|
-
|
102
|
+
end
|
103
|
+
|
102
104
|
# Enable unscoped searching
|
103
105
|
if options[:unscoped] == true
|
104
106
|
results = self.unscoped.where(:id => result_ids)
|
105
|
-
else
|
107
|
+
else
|
106
108
|
results = self.where(:id => result_ids)
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
|
109
|
+
end
|
110
|
+
|
111
111
|
if options[:conditions]
|
112
112
|
results = results.where(options[:conditions])
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
if !self.condition_default.blank?
|
116
116
|
results = results.where(self.condition_default)
|
117
|
-
end
|
118
|
-
|
119
|
-
if options[:group]
|
117
|
+
end
|
118
|
+
|
119
|
+
if options[:group]
|
120
120
|
results = results.group(options[:group])
|
121
|
-
end
|
122
|
-
|
121
|
+
end
|
122
|
+
|
123
123
|
if options[:order] || self.order_default
|
124
124
|
results = results.order(options[:order] || self.order_default)
|
125
125
|
else
|
126
126
|
ordered_results = []
|
127
|
-
results.each do |r|
|
127
|
+
results.each do |r|
|
128
128
|
r.query_score = result_scores[r.id]
|
129
129
|
ordered_results[result_ids.index(r.id)] = r
|
130
|
-
end
|
131
|
-
|
130
|
+
end
|
131
|
+
|
132
132
|
results = ordered_results.compact
|
133
|
-
end
|
134
|
-
|
133
|
+
end
|
134
|
+
|
135
135
|
return results
|
136
|
-
else
|
136
|
+
else
|
137
137
|
raise "#{self.inspect} is not a SmartSearch"
|
138
|
-
end
|
138
|
+
end
|
139
139
|
end
|
140
|
-
|
140
|
+
|
141
141
|
# reload search_tags for entire table based on the attributes defined in ':on' option passed to the 'smart_search' method
|
142
142
|
def set_search_index
|
143
143
|
s = self.all.size.to_f
|
144
144
|
self.all.each_with_index do |a, i|
|
145
145
|
a.create_search_tags
|
146
146
|
done = ((i+1).to_f/s)*100
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
151
150
|
# Load all search tags for this table into similarity index
|
152
151
|
def set_similarity_index
|
153
|
-
|
154
152
|
search_tags_list = self.connection.select_all("SELECT search_tags from #{SmartSearchTag.table_name} where `table_name` = #{self.table_name}").map {|r| r["search_tags"]}
|
155
|
-
|
153
|
+
|
156
154
|
SmartSimilarity.create_from_text(search_tags_list.join(" "))
|
157
|
-
end
|
158
|
-
|
159
|
-
end
|
160
|
-
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
161
159
|
# Instance Methods for ActiveRecord
|
162
160
|
module InstanceMethods
|
163
|
-
|
161
|
+
|
164
162
|
# Load the result template path for this instance
|
165
163
|
def result_template_path
|
166
164
|
self.class.result_template_path
|
167
|
-
end
|
168
|
-
|
165
|
+
end
|
166
|
+
|
167
|
+
def dont_update_search_tags!
|
168
|
+
self.dont_update_search_tags = true
|
169
|
+
end
|
170
|
+
|
171
|
+
def update_search_tags?
|
172
|
+
!self.dont_update_search_tags
|
173
|
+
end
|
174
|
+
|
169
175
|
# create search tags for this very record based on the attributes defined in ':on' option passed to the 'Class.smart_search' method
|
170
176
|
def create_search_tags
|
171
177
|
tags = []
|
172
|
-
|
178
|
+
|
173
179
|
self.class.tags.each do |tag|
|
174
|
-
|
180
|
+
|
175
181
|
if !tag.is_a?(Hash)
|
176
|
-
tag = {:field_name => tag, :boost => 1, :search_tags => ""}
|
182
|
+
tag = {:field_name => tag, :boost => 1, :search_tags => ""}
|
177
183
|
else
|
178
184
|
tag[:search_tags] = ""
|
179
185
|
tag[:boost] ||= 1
|
180
|
-
end
|
181
|
-
|
186
|
+
end
|
187
|
+
|
182
188
|
if tag[:field_name].is_a?(Symbol)
|
183
189
|
tag[:search_tags] << self.send(tag[:field_name]).to_s
|
184
190
|
elsif tag[:field_name].is_a?(String)
|
185
|
-
tag_methods = tag[:field_name].split(".")
|
191
|
+
tag_methods = tag[:field_name].split(".")
|
186
192
|
tagx = self.send(tag_methods[0])
|
187
193
|
tag_methods[1..-1].each do |x|
|
188
194
|
tagx = tagx.send(x) rescue ""
|
189
195
|
end
|
190
|
-
tag[:search_tags] << tagx.to_s
|
196
|
+
tag[:search_tags] << tagx.to_s
|
191
197
|
end
|
192
|
-
|
193
|
-
tag[:search_tags] = tag[:search_tags].split(" ").uniq.join(" ").downcase.clear_html
|
198
|
+
|
199
|
+
tag[:search_tags] = tag[:search_tags].split(" ").uniq.join(" ").downcase.clear_html
|
194
200
|
tags << tag
|
195
201
|
end
|
196
|
-
|
197
|
-
|
202
|
+
|
203
|
+
|
198
204
|
self.clear_search_tags
|
199
|
-
|
205
|
+
|
200
206
|
# Merge search tags with same boost
|
201
207
|
@merged_tags = {}
|
202
|
-
|
208
|
+
|
203
209
|
tags.each do |t|
|
204
210
|
boost = t[:boost]
|
205
|
-
|
211
|
+
|
206
212
|
if @merged_tags[boost]
|
207
|
-
|
213
|
+
|
208
214
|
@merged_tags[boost][:field_name] << ",#{t[:field_name]}"
|
209
215
|
@merged_tags[boost][:search_tags] << " #{t[:search_tags]}"
|
210
216
|
else
|
211
217
|
@merged_tags[boost] = {:field_name => "#{t[:field_name]}", :search_tags => t[:search_tags], :boost => boost }
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|
215
|
-
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
216
222
|
@merged_tags.values.each do |t|
|
217
223
|
if !t[:search_tags].blank? && t[:search_tags].size > 1
|
218
|
-
SmartSearchTag.create(t.merge!(:table_name => self.class.table_name, :entry_id => self.id, :search_tags => t[:search_tags].strip.split(" ").uniq.join(" ")))
|
219
|
-
end
|
224
|
+
SmartSearchTag.create(t.merge!(:table_name => self.class.table_name, :entry_id => self.id, :search_tags => t[:search_tags].strip.split(" ").uniq.join(" ")))
|
225
|
+
end
|
220
226
|
end
|
221
|
-
|
227
|
+
|
222
228
|
end
|
223
|
-
|
229
|
+
|
224
230
|
# Remove search data for the instance from the index
|
225
231
|
def clear_search_tags
|
226
232
|
if !self.id.nil?
|
227
|
-
SmartSearchTag.connection.execute("DELETE from #{SmartSearchTag.table_name} where `table_name` = '#{self.class.table_name}' and entry_id = #{self.id}")
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
end
|
232
|
-
|
233
|
+
SmartSearchTag.connection.execute("DELETE from #{SmartSearchTag.table_name} where `table_name` = '#{self.class.table_name}' and entry_id = #{self.id}") rescue nil
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
233
239
|
|
234
240
|
class Config
|
235
|
-
|
241
|
+
|
236
242
|
cattr_accessor :search_models
|
237
243
|
cattr_accessor :public_models
|
238
|
-
|
244
|
+
|
239
245
|
self.search_models = []
|
240
246
|
self.public_models = []
|
241
|
-
|
247
|
+
|
242
248
|
def self.get_search_models
|
243
|
-
self.search_models.map {|m| m.constantize}
|
249
|
+
self.search_models.map {|m| m.constantize}
|
244
250
|
end
|
245
|
-
|
251
|
+
|
246
252
|
def self.get_public_models
|
247
|
-
self.public_models.map {|m| m.constantize}
|
248
|
-
end
|
249
|
-
|
250
|
-
end
|
251
|
-
|
252
|
-
|
253
|
+
self.public_models.map {|m| m.constantize}
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
|
253
259
|
end
|
254
260
|
|
255
261
|
|
data/lib/tasks/smart_search.rake
CHANGED
@@ -4,7 +4,7 @@ namespace :smart_search do
|
|
4
4
|
require File.expand_path("../../smart_similarity", __FILE__)
|
5
5
|
SmartSimilarity.load_from_query_history
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
desc "Load similarity data from file - Use FILE=path/to/file to specify file"
|
9
9
|
task :similarity_from_file => :environment do
|
10
10
|
require File.expand_path("../../smart_similarity", __FILE__)
|
@@ -12,47 +12,47 @@ namespace :smart_search do
|
|
12
12
|
raise ArgumentError, "No file specified. "
|
13
13
|
elsif !File.exist?(ENV['FILE_PATH'])
|
14
14
|
raise ArgumentError, "File not found "
|
15
|
-
else
|
15
|
+
else
|
16
16
|
SmartSimilarity.load_file(ENV['FILE_PATH'])
|
17
|
-
end
|
17
|
+
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
desc "Load similarity data from url - Use URL=http://.../ to specify url - Requires 'curl'"
|
21
21
|
task :similarity_from_url => :environment do
|
22
22
|
require File.expand_path("../../smart_similarity", __FILE__)
|
23
23
|
if ENV['URL'].nil?
|
24
24
|
raise ArgumentError, "No URL specified. "
|
25
|
-
else
|
25
|
+
else
|
26
26
|
SmartSimilarity.load_url(ENV['URL'])
|
27
|
-
end
|
27
|
+
end
|
28
28
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
|
30
|
+
|
31
|
+
|
32
32
|
desc "load ignore words list"
|
33
33
|
task :load_ignore_words => :environment do
|
34
34
|
require File.expand_path("../../smart_search_ignore_word", __FILE__)
|
35
|
-
|
35
|
+
|
36
36
|
dic_path = File.expand_path("../../../dictionaries/*", __FILE__)
|
37
|
-
|
37
|
+
|
38
38
|
raise dic_path.inspect
|
39
|
-
|
39
|
+
|
40
40
|
dic_folders = Dir.glob(dic_path).select {|d| File.directory?(d)}
|
41
|
-
|
41
|
+
|
42
42
|
dic_folders.each do |folder|
|
43
43
|
locale = folder.split("/").last
|
44
44
|
word_file = File.join(folder, "#{locale}.ignore_words.dic")
|
45
45
|
if File.exists?(word_file)
|
46
46
|
File.open(word_file, "r").each_line do |word|
|
47
47
|
SmartSearchIgnoreWord.create(:word => word.strip.downcase, :locale => locale)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
57
|
|
58
58
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Eck
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 4.0.4
|
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
|
-
version:
|
26
|
+
version: 4.0.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: amatch
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: friendly_extensions
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: mysql2
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,7 +69,7 @@ dependencies:
|
|
69
69
|
description: SmartSearch adds full-text search functions to ActiveRecord running with
|
70
70
|
MySQL, including search for similiar words. Its fast, simple, and works with almost
|
71
71
|
zero-config!
|
72
|
-
email:
|
72
|
+
email: florian.eck@el-digital.de
|
73
73
|
executables: []
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
version: '0'
|
110
110
|
requirements: []
|
111
111
|
rubyforge_project:
|
112
|
-
rubygems_version: 2.
|
112
|
+
rubygems_version: 2.6.12
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: Simple, easy to use search MySQL based search for ActiveRecord
|
@@ -120,4 +120,3 @@ test_files:
|
|
120
120
|
- test/unit/01_smart_search_test.rb
|
121
121
|
- test/unit/02_smart_search_similarity_test.rb
|
122
122
|
- test/unit/03_smart_search_boost_test.rb
|
123
|
-
has_rdoc:
|