smart_search 0.0.65 → 0.0.67

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09683d827340bc1dc832274e31723252da084649
4
- data.tar.gz: 580b05753d0355b99502205b99276bf71eb6bf58
3
+ metadata.gz: ea2edfd305ac9960e74f36148bc8710e0b877416
4
+ data.tar.gz: 0d7d5bd0c94c3624d11d83fded3abcd6cb1a42a3
5
5
  SHA512:
6
- metadata.gz: 54a44b78402531aa5098329681e7929f3f97671e42cd5bbe5b517e5143bfa4b9e671179a3d99b537fe8c43de770929ee3c7041be4d2f28197be242bedb774333
7
- data.tar.gz: 89df4b626f3f3979a509f637aa5112c6452cf1394108223ba80f603d36534d24331e5864d0ca1d6f85539cf6430cf291a4fdaf385b92bfef6ec17c9358b21fa2
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
- if self.column_names.include?("created_at")
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 = SmartSearchTag.connection.select_all("select entry_id, sum(boost), group_concat(search_tags) as grouped_tags
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 sum(boost) DESC").map {|r| r["entry_id"]}
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
- results = results.order(self.order_default)
116
- end
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 rescue nil
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}")
@@ -1,5 +1,7 @@
1
+ # :nodoc:
1
2
  module SmartSearch
2
3
  require "rails"
4
+ # :nodoc:
3
5
  class SmartSearchEngine < Rails::Engine
4
6
  isolate_namespace SmartSearch
5
7
  require "friendly_extensions"
@@ -1,3 +1,5 @@
1
+ # The keep words out of the index, they can be added into this table
2
+ # TODO: Its not working yet
1
3
  class SmartSearchIgnoreWord < ActiveRecord::Base
2
4
 
3
5
  #= Configuration
@@ -1,2 +1,3 @@
1
+ # Represents the search index
1
2
  class SmartSearchTag < ActiveRecord::Base
2
3
  end
@@ -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.8
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
- current += prepared_text.select {|w| w != word && self.match_words(w,word) >= SIMILARITY_FACTOR}
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"]}.join(" ")
105
- self.create_from_text(queries)
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 test_result_should_be_redefinable
67
- user_c = User.create(:first_name => "C", :last_name => "Next1")
68
- user_a = User.create(:first_name => "A", :last_name => "Bah")
69
- user_b = User.create(:first_name => "B", :last_name => "Next2")
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
- assert_equal [], User.find_by_tags("A").where("last_name <> 'Bah' ")
72
- end
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.65
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-02-16 00:00:00.000000000 Z
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, including
70
- search for similiar words. Its fast, simple, and works with almost zero-config!
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
- homepage: https://rubygems.org/gems/smart_search
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: