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 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: