smart_search 0.0.7 → 0.0.8
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 +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:
|