smart_search 0.0.61 → 0.0.65

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: d1dba8372dab82dd2cce0ebc37101e2ebcf2fba1
4
- data.tar.gz: a71c65f0d8adac611ce88e75a43ea30d36a5d63a
3
+ metadata.gz: 09683d827340bc1dc832274e31723252da084649
4
+ data.tar.gz: 580b05753d0355b99502205b99276bf71eb6bf58
5
5
  SHA512:
6
- metadata.gz: 6a932397b5b2eafae190335567419028ef0ad2547c2156c1aab1ca5b1aed4204caaa646c82bd1f0b062eecc88662f06ba06422b0c59336ff1cfc910c670c1e27
7
- data.tar.gz: 6b544662bb2360c1bba224a580d02fc8d5b46153200d77734f2297471b8c67d7362deadc7c93d6fbbf7cd961617e9cb48fa8bb373e14584b88688cbe21edbbde
6
+ metadata.gz: 54a44b78402531aa5098329681e7929f3f97671e42cd5bbe5b517e5143bfa4b9e671179a3d99b537fe8c43de770929ee3c7041be4d2f28197be242bedb774333
7
+ data.tar.gz: 89df4b626f3f3979a509f637aa5112c6452cf1394108223ba80f603d36534d24331e5864d0ca1d6f85539cf6430cf291a4fdaf385b92bfef6ec17c9358b21fa2
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: mysql2
3
+ database: smart_search_test
4
+ username: travis
5
+ password:
6
+ encoding: utf8
@@ -1,5 +1,8 @@
1
1
  module SmartSearch
2
+ require "rails"
2
3
  class SmartSearchEngine < Rails::Engine
3
4
  isolate_namespace SmartSearch
5
+ require "friendly_extensions"
6
+ require "amatch"
4
7
  end
5
8
  end
data/lib/smart_search.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ require "rails"
3
+
2
4
  require "smart_search"
5
+ require "smart_search/smart_search_engine"
6
+
3
7
  require "smart_similarity"
4
8
  require "smart_search_history"
5
- require "add_search_tags"
9
+ require "smart_search_tag"
6
10
 
7
- require "smart_search/smart_search_engine"
8
11
 
9
12
  module SmartSearch
10
13
 
@@ -27,12 +30,9 @@ module SmartSearch
27
30
 
28
31
  cattr_accessor :condition_default, :group_default, :tags, :order_default, :enable_similarity
29
32
  send :include, InstanceMethods
30
- if self.column_names.index("search_tags").nil?
31
- ::AddSearchTags.add_to_table(self.table_name)
32
- end
33
- self.send(:before_save, :create_search_tags)
34
-
35
- self.enable_similarity = true
33
+ self.send(:after_save, :create_search_tags)
34
+ self.send(:before_destroy, :create_search_tags)
35
+ self.enable_similarity ||= true
36
36
 
37
37
  # options zuweisen
38
38
  if options[:conditions].is_a?(String) && !options[:conditions].blank?
@@ -89,8 +89,13 @@ module SmartSearch
89
89
  tags.map! {|t| "search_tags LIKE '%#{t}%'"}
90
90
  end
91
91
 
92
+ # Load ranking from Search tags
93
+ result_ids = SmartSearchTag.connection.select_all("select entry_id, sum(boost), group_concat(search_tags) as grouped_tags
94
+ from smart_search_tags where `table_name`= '#{self.table_name}' and
95
+
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"]}
92
97
 
93
- results = self.where("(#{tags.join(' AND ')})")
98
+ results = self.where(:id => result_ids)
94
99
 
95
100
  if options[:conditions]
96
101
  results = results.where(options[:conditions])
@@ -127,10 +132,10 @@ module SmartSearch
127
132
  end
128
133
  end
129
134
 
130
- # Create all search tags for this table into similarity index
135
+ # Load all search tags for this table into similarity index
131
136
  def set_similarity_index
132
137
 
133
- search_tags_list = self.connection.select_all("SELECT search_tags from #{self.table_name}").map {|r| r["search_tags"]}
138
+ 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"]}
134
139
 
135
140
  SmartSimilarity.create_from_text(search_tags_list.join(" "))
136
141
  end
@@ -145,29 +150,51 @@ module SmartSearch
145
150
 
146
151
  # create search tags for this very record based on the attributes defined in ':on' option passed to the 'Class.smart_search' method
147
152
  def create_search_tags
148
- tags = []
153
+ tags = []
154
+
149
155
  self.class.tags.each do |tag|
150
- if tag.is_a?(Symbol)
151
- tags << self.send(tag)
152
- elsif tag.is_a?(String)
153
- tag_methods = tag.split(".")
156
+
157
+ if !tag.is_a?(Hash)
158
+ tag = {:field_name => tag, :boost => 1, :search_tags => ""}
159
+ else
160
+ tag[:search_tags] = ""
161
+ tag[:boost] ||= 1
162
+ end
163
+
164
+ if tag[:field_name].is_a?(Symbol)
165
+ tag[:search_tags] << self.send(tag[:field_name]).to_s
166
+ elsif tag[:field_name].is_a?(String)
167
+ tag_methods = tag[:field_name].split(".")
154
168
  tagx = self.send(tag_methods[0])
155
169
  tag_methods[1..-1].each do |x|
156
170
  tagx = tagx.send(x) rescue ""
157
171
  end
158
- tags << tagx
159
- end
172
+ tag[:search_tags] << tagx.to_s
173
+ end
174
+
175
+ tag[:search_tags] = tag[:search_tags].split(" ").uniq.join(" ").downcase
176
+ tags << tag
160
177
  end
161
- searchtags = tags.join(" ").split(" ")
162
- searchtags = searchtags.uniq.join(" ")
163
- search_tags_min = searchtags.gsub(" ", "").downcase
164
178
 
165
- self.search_tags = "#{searchtags.downcase}"
166
- end
179
+
180
+ self.clear_search_tags
181
+
182
+ tags.each do |t|
183
+ SmartSearchTag.create(t.merge!(:table_name => self.class.table_name, :entry_id => self.id))
184
+ end
185
+
186
+
187
+ end
188
+
189
+ def clear_search_tags
190
+ if !self.id.nil?
191
+ SmartSearchTag.connection.execute("DELETE from #{SmartSearchTag.table_name} where `table_name` = '#{self.class.table_name}' and entry_id = #{self.id}")
192
+ end
193
+ end
167
194
 
168
195
  end
169
196
 
170
-
197
+
171
198
  class Config
172
199
 
173
200
  cattr_accessor :search_models
@@ -0,0 +1,28 @@
1
+ class SmartSearchIgnoreWord < ActiveRecord::Base
2
+
3
+ #= Configuration
4
+ self.table_name = "smart_search_ignore_words"
5
+ #== Associations
6
+ # => Stuff in Here
7
+
8
+ #== Plugins and modules
9
+ #=== PlugIns
10
+ # => Stuff in Here
11
+
12
+ #=== include Modules
13
+ # => Stuff in Here
14
+
15
+ #== Konstanten
16
+ # => Stuff in Here
17
+
18
+ #== Validation and Callbacks
19
+ #=== Validation
20
+ validates_uniqueness_of :word
21
+
22
+ #=== Callbacks
23
+ # => Stuff in Here
24
+
25
+
26
+ # => END
27
+
28
+ end
@@ -0,0 +1,2 @@
1
+ class SmartSearchTag < ActiveRecord::Base
2
+ end
@@ -3,7 +3,6 @@ class SmartSimilarity < ActiveRecord::Base
3
3
  #= Configuration
4
4
  serialize :similarities, Array
5
5
  self.table_name = "smart_search_similarities"
6
- require "amatch"
7
6
 
8
7
  #== Associations
9
8
  # => Stuff in Here
@@ -28,6 +27,8 @@ class SmartSimilarity < ActiveRecord::Base
28
27
  # Limit Number of similar words
29
28
  SIMILARITY_LIMIT = 8
30
29
 
30
+ SPLITTING_REGEXP = /\b/
31
+
31
32
  #== Validation and Callbacks
32
33
  #=== Validation
33
34
 
@@ -39,7 +40,7 @@ class SmartSimilarity < ActiveRecord::Base
39
40
  # This method is used to generate date from every source, e.g. file, url, single words etc..
40
41
  def self.create_from_text(text)
41
42
  # prepare text
42
- prepared_text = text.downcase.split(/\b/).uniq
43
+ prepared_text = text.downcase.split(SPLITTING_REGEXP).uniq
43
44
  prepared_text = prepared_text.select {|w| w.size >= 3 && !w.match(/[0-9\-_<>\.\/(){}&\?"'@*+$!=,:'#;]/)}
44
45
  list = {}
45
46
  prepared_text.each do |word|
@@ -78,18 +79,19 @@ class SmartSimilarity < ActiveRecord::Base
78
79
  # Best used for loading big dictionary files.
79
80
  # Uses 'spawnling' to split the data into 8 stacks and load them simultaniously
80
81
  def self.load_file(path)
81
- count = %x{wc -l #{path}}.split[0].to_i
82
+ count = %x{wc -l #{path}}.split[0].to_i.max(1)
82
83
  puts "loading file: #{path}"
83
84
  puts "=> #{count} rows"
84
- File.open(path, "r").to_a.seperate([8,count].min).each_with_index do |stack, si|
85
- Spawnling.new(:argv => "sim-file-#{si}") do
86
- QueryLog.info "sim-file-#{si}"
87
- stack.each_with_index do |l,i|
88
- QueryLog.info "#{si}: #{i.fdiv(count).round(4)} %"
89
- self.add_word(l)
90
- end
91
- end
92
- end
85
+
86
+ if count == 1
87
+ File.open(path, "r").read.split(SPLITTING_REGEXP).each {|w| self.add_word(w)}
88
+ else
89
+ File.open(path, "r").read.split(SPLITTING_REGEXP).seperate([8,count].min).each_with_index do |stack, si|
90
+ stack.each_with_index do |l,i|
91
+ self.add_word(l)
92
+ end
93
+ end
94
+ end
93
95
  end
94
96
 
95
97
  # Load words from website and save them to index
@@ -99,7 +101,7 @@ class SmartSimilarity < ActiveRecord::Base
99
101
 
100
102
  # Loads your created query history and saves them to the index
101
103
  def self.load_from_query_history
102
- queries = self.connection.select_all("SELECT query from `#{::SmartSearchHistory.table_name}`").map {|r| r["query"]}.join(" ")
104
+ queries = ActiveRecord::Base.connection.select_all("SELECT query from `#{::SmartSearchHistory.table_name}`").map {|r| r["query"]}.join(" ")
103
105
  self.create_from_text(queries)
104
106
  self.connection.execute("TRUNCATE `#{::SmartSearchHistory.table_name}`")
105
107
  end
@@ -110,7 +112,7 @@ class SmartSimilarity < ActiveRecord::Base
110
112
  if list.nil?
111
113
  return [word]
112
114
  else
113
- self.increment_counter(:count, list.id)
115
+ self.connection.execute("UPDATE `smart_search_similarities` SET `count` = #{list.count+1} where `smart_search_similarities`.`phrase` = '#{list.phrase}'")
114
116
  return [word, list.similarities].flatten
115
117
  end
116
118
  end
@@ -26,5 +26,33 @@ namespace :smart_search do
26
26
  SmartSimilarity.load_url(ENV['URL'])
27
27
  end
28
28
  end
29
+
30
+
31
+
32
+ desc "load ignore words list"
33
+ task :load_ignore_words => :environment do
34
+ require File.expand_path("../../smart_search_ignore_word", __FILE__)
35
+
36
+ dic_path = File.expand_path("../../../dictionaries/*", __FILE__)
37
+
38
+ raise dic_path.inspect
39
+
40
+ dic_folders = Dir.glob(dic_path).select {|d| File.directory?(d)}
41
+
42
+ dic_folders.each do |folder|
43
+ locale = folder.split("/").last
44
+ word_file = File.join(folder, "#{locale}.ignore_words.dic")
45
+ if File.exists?(word_file)
46
+ File.open(word_file, "r").each_line do |word|
47
+ SmartSearchIgnoreWord.create(:word => word.strip.downcase, :locale => locale)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+
29
55
 
30
- end
56
+ end
57
+
58
+
@@ -0,0 +1,27 @@
1
+ require "active_record"
2
+ namespace :db do
3
+ desc "Create test database. Overwrite dasebase config with USERNAME=, PASSWORD=, DATABASE="
4
+ task :create_test_db do
5
+ config = YAML::load(File.open(File.expand_path("config/database.yml")))["test"]
6
+
7
+ # Overwrite config
8
+ config.merge!('database' => ENV['DATABASE']) if ENV['DATABASE']
9
+ config.merge!('username' => ENV['USERNAME']) if ENV['USERNAME']
10
+ config.merge!('password' => ENV['PASSWORD']) if ENV['PASSWORD']
11
+
12
+ ActiveRecord::Base.establish_connection(config.merge('database' => nil))
13
+ ActiveRecord::Base.connection.drop_database(config['database']) rescue nil
14
+ ActiveRecord::Base.connection.create_database(config['database'])
15
+ ActiveRecord::Base.establish_connection(config)
16
+ end
17
+
18
+ task :migrate do
19
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths)
20
+ end
21
+ end
22
+
23
+ task :test_smart_search do
24
+ Rake::Task["db:create_test_db"].execute
25
+ Rake::Task["db:migrate"].execute
26
+ Rake::Task["test"].execute
27
+ end
@@ -0,0 +1,6 @@
1
+ The quick brown fox jumps over the lazy dog.
2
+ The five boxing wizards jump quickly.
3
+ Sphinx of all black quartz judge my vow.
4
+ A quick movement of the enemy will jeopardize six gunboats.
5
+ Five quacking Zephyrs jolt my wax bed.
6
+ Heavy boxes perform waltzes and jigs.
@@ -0,0 +1 @@
1
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
data/test/test_helper.rb CHANGED
@@ -1,14 +1,16 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'rubygems'
3
+ require 'bundler'
3
4
  require 'test/unit'
4
5
  require 'active_support'
5
6
  require 'active_record'
6
7
  require 'active_model'
7
8
 
8
9
  require "smart_search"
9
- require "add_search_tags"
10
+ require "smart_search/smart_search_engine"
10
11
 
11
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
12
+
13
+ ActiveRecord::Base.establish_connection(:adapter => "mysql2", :database => "smart_search_test")
12
14
 
13
15
  ActiveRecord::Schema.define(:version => 1) do
14
16
  create_table :users do |t|
@@ -16,7 +18,6 @@ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":me
16
18
  t.string :last_name
17
19
  t.integer :office_id
18
20
  t.date :birthday
19
- t.text :search_tags
20
21
  t.timestamps
21
22
  end
22
23
 
@@ -25,15 +26,15 @@ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":me
25
26
  t.string :last_name
26
27
  t.integer :user_id
27
28
  t.date :birthday
28
- t.text :search_tags
29
29
  t.timestamps
30
30
  end
31
31
 
32
32
  create_table :offices do |t|
33
33
  t.string :name
34
- t.text :search_tags
35
34
  t.timestamps
36
- end
35
+ end
36
+
37
+
37
38
  end
38
39
  #
39
40
  # def drop_db
@@ -6,6 +6,8 @@ class SmartSearchTest < Test::Unit::TestCase
6
6
  office_name = "Office1"
7
7
  office = Office.create(:name => office_name)
8
8
 
9
+ Office.enable_similarity = false
10
+
9
11
  assert_equal office, Office.find_by_tags(office_name).first
10
12
  end
11
13
 
@@ -36,6 +38,7 @@ class SmartSearchTest < Test::Unit::TestCase
36
38
  office_id_nok = 5
37
39
 
38
40
  User.smart_search :on => [:full_name], :conditions => "office_id <> #{office_id_nok}", :force => true
41
+ User.enable_similarity = false
39
42
 
40
43
  user = User.create(:first_name => "Unknown", :last_name => "User", :office_id => office_id_nok)
41
44
  user = User.create(:first_name => "Public", :last_name => "User", :office_id => office_id_ok)
@@ -46,6 +49,7 @@ class SmartSearchTest < Test::Unit::TestCase
46
49
 
47
50
  def test_should_use_default_order_and_order_should_be_overwriteable
48
51
  User.smart_search :on => [:full_name], :order => :first_name, :force => true
52
+ User.enable_similarity = false
49
53
 
50
54
  user_c = User.create(:first_name => "C", :last_name => "Test1")
51
55
  user_a = User.create(:first_name => "A", :last_name => "Test3")
@@ -65,9 +69,7 @@ class SmartSearchTest < Test::Unit::TestCase
65
69
  user_b = User.create(:first_name => "B", :last_name => "Next2")
66
70
 
67
71
  assert_equal [], User.find_by_tags("A").where("last_name <> 'Bah' ")
68
- end
69
-
70
-
72
+ end
71
73
 
72
74
 
73
75
  end
@@ -0,0 +1,32 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "test_helper"
3
+ class SmartSearchSimilartyTest < Test::Unit::TestCase
4
+
5
+ def test_similarity_should_load_from_file
6
+ testfile_1 = File.expand_path("../../test_document_one_line.txt", __FILE__)
7
+ testfile_2 = File.expand_path("../../test_document_multi_line.txt", __FILE__)
8
+ SmartSimilarity.connection.execute "Truncate table #{SmartSimilarity.table_name}"
9
+
10
+ assert_equal 0, SmartSimilarity.count
11
+ SmartSimilarity.load_file(testfile_2)
12
+ new_count = SmartSimilarity.count
13
+ assert_not_equal 0, new_count
14
+
15
+ SmartSimilarity.load_file(testfile_1)
16
+ assert_not_equal new_count, SmartSimilarity.count
17
+ end
18
+
19
+ def test_similarity_should_load_from_url
20
+ count = SmartSimilarity.count
21
+ SmartSimilarity.load_url("https://github.com/florianeck/smart_search")
22
+ assert_not_equal count, SmartSimilarity.count
23
+ end
24
+
25
+ def test_similarity_should_load_from_history
26
+ count = SmartSimilarity.count
27
+ User.find_by_tags("this is history now")
28
+ SmartSimilarity.load_from_query_history
29
+ assert_not_equal count, SmartSimilarity.count
30
+ end
31
+
32
+ end
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.61
4
+ version: 0.0.65
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-09 00:00:00.000000000 Z
11
+ date: 2014-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -39,49 +39,55 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: spawnling
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.0.61
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.0.61
55
55
  - !ruby/object:Gem::Dependency
56
- name: friendly_extensions
56
+ name: mysql2
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 0.0.61
61
+ version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 0.0.61
69
- description: Adds easy to use full-text search to ActiveRecord models, based the attributes
70
- you want to search.
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!
71
71
  email: it-support@friends-systems.de
72
72
  executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - config/database.yml
76
77
  - config/routes.rb
77
- - lib/add_search_tags.rb
78
78
  - lib/smart_search.rb
79
79
  - lib/smart_search/smart_search_engine.rb
80
80
  - lib/smart_search_history.rb
81
+ - lib/smart_search_ignore_word.rb
82
+ - lib/smart_search_tag.rb
81
83
  - lib/smart_similarity.rb
82
84
  - lib/tasks/smart_search.rake
85
+ - lib/tasks/testing.rake
86
+ - test/test_document_multi_line.txt
87
+ - test/test_document_one_line.txt
83
88
  - test/test_helper.rb
84
- - test/unit/smart_search_test.rb
89
+ - test/unit/01_smart_search_test.rb
90
+ - test/unit/02_smart_search_similarity_test.rb
85
91
  homepage: https://rubygems.org/gems/smart_search
86
92
  licenses: []
87
93
  metadata: {}
@@ -104,7 +110,10 @@ rubyforge_project:
104
110
  rubygems_version: 2.2.1
105
111
  signing_key:
106
112
  specification_version: 4
107
- summary: Simple, easy to use search.
113
+ summary: Simple, easy to use search MySQL based search for ActiveRecord
108
114
  test_files:
115
+ - test/test_document_multi_line.txt
116
+ - test/test_document_one_line.txt
109
117
  - test/test_helper.rb
110
- - test/unit/smart_search_test.rb
118
+ - test/unit/01_smart_search_test.rb
119
+ - test/unit/02_smart_search_similarity_test.rb
@@ -1,8 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
- class AddSearchTags < ActiveRecord::Migration
3
-
4
- def self.add_to_table(table_name)
5
- add_column table_name, :search_tags, :text
6
- end
7
-
8
- end