search_qd 0.0.1
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.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +55 -0
- data/Rakefile +52 -0
- data/lib/search_qd.rb +67 -0
- data/lib/search_qd/rails.rb +7 -0
- data/lib/search_qd/version.rb +3 -0
- data/search_qd.gemspec +25 -0
- data/spec/db_config.yml.example +4 -0
- data/spec/search_qd_spec.rb +92 -0
- data/spec/spec_helper.rb +5 -0
- metadata +97 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Karsten Gallinowski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# SearchQd
|
2
|
+
|
3
|
+
## DESCRIPTION
|
4
|
+
|
5
|
+
SearchQd extends ActiveRecord and offers a method called search\_qd
|
6
|
+
for a simple full text search. Simple because the search functionality is
|
7
|
+
executed as SQL LIKE statements for given (text) columns.
|
8
|
+
|
9
|
+
## Quick start
|
10
|
+
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem install search_qd
|
14
|
+
```
|
15
|
+
|
16
|
+
* Rails 3
|
17
|
+
|
18
|
+
Add the following line to your Gemfile
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'search_qd'
|
22
|
+
```
|
23
|
+
|
24
|
+
* ActiveRecord outside of Rails
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'search_qd'
|
28
|
+
ActiveRecord::Base.send(:include, SearchQd)
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
Assuming your model name is Blog and the model has two text columns title and content:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class Blog < ActiveRecord::Base
|
37
|
+
search_qd_columns :title, :content
|
38
|
+
end
|
39
|
+
|
40
|
+
Blog.search_qd("some text to seach for") # search for 'some text to search for' in title and content column
|
41
|
+
Blog.search_qd("some text to search for", "title") # search for 'some text to search for' in title column
|
42
|
+
Blog.search_qd("some text to search for", "user_name") # search for 'some text to search for' in user_name column
|
43
|
+
```
|
44
|
+
|
45
|
+
As shown in the example above the search\_qd method expects the search query as a string and a list of columns. If no list
|
46
|
+
of columns is given, the search\_qd method uses the column list defined by search\_qd\_columns (see class Blog definition).
|
47
|
+
|
48
|
+
## REQUIREMENTS
|
49
|
+
|
50
|
+
* ActiveRecord
|
51
|
+
* Ruby 1.9.\*
|
52
|
+
|
53
|
+
## License
|
54
|
+
|
55
|
+
This gem is created by Karsten Gallinowski and released under the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default do
|
8
|
+
Rake::Task["db:setup"].invoke
|
9
|
+
Rake::Task[:spec].invoke
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :db do
|
13
|
+
desc 'Create and configure test database'
|
14
|
+
task :setup do
|
15
|
+
spec_dir = File.expand_path(File.dirname(__FILE__) + '/spec')
|
16
|
+
db_config = "#{spec_dir}/db_config.yml"
|
17
|
+
|
18
|
+
unless File.exists?(db_config)
|
19
|
+
STDOUT.puts "Database configuration file #{db_config} could not been found."
|
20
|
+
else
|
21
|
+
STDOUT.puts "Try running migrations"
|
22
|
+
Rake::Task["db:drop"].invoke
|
23
|
+
Rake::Task["db:migrate"].invoke
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Run migrations for test database'
|
28
|
+
task :migrate do
|
29
|
+
spec_dir = File.expand_path(File.dirname(__FILE__) + '/spec')
|
30
|
+
db_config = "#{spec_dir}/db_config.yml"
|
31
|
+
|
32
|
+
ActiveRecord::Base.establish_connection(YAML.load_file(db_config))
|
33
|
+
ActiveRecord::Migration.instance_eval do
|
34
|
+
create_table :blogs do |t|
|
35
|
+
t.string :title
|
36
|
+
t.text :content
|
37
|
+
t.string :user_name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Drop tables from test database'
|
43
|
+
task :drop do
|
44
|
+
spec_dir = File.expand_path(File.dirname(__FILE__) + '/spec')
|
45
|
+
db_config = "#{spec_dir}/db_config.yml"
|
46
|
+
|
47
|
+
ActiveRecord::Base.establish_connection(YAML.load_file(db_config))
|
48
|
+
ActiveRecord::Migration.instance_eval do
|
49
|
+
drop_table :blogs if self.table_exists?(:blogs)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/search_qd.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'search_qd/version'
|
3
|
+
require 'active_record' unless defined? Rails
|
4
|
+
require 'search_qd/rails' if defined? Rails
|
5
|
+
|
6
|
+
module SearchQd
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# method which can be used inside class definitions
|
13
|
+
# to define the default search columns of the class
|
14
|
+
# and to extend the class with search_qd singleton method
|
15
|
+
def search_qd_columns(*column_names)
|
16
|
+
@searchable_columns = []
|
17
|
+
[column_names].flatten.each do |column_name|
|
18
|
+
@searchable_columns << column_name
|
19
|
+
end
|
20
|
+
extend SearchQd::SingletonMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module SingletonMethods
|
25
|
+
# search method which provides possibility
|
26
|
+
# to define search columns (fields) if default
|
27
|
+
# columns should not be used
|
28
|
+
def search_qd(query, fields=nil)
|
29
|
+
where(search_qd_conditions(query, fields))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# method to create plain SQL LIKE
|
35
|
+
# statements for each search term and search column
|
36
|
+
#
|
37
|
+
# SQL injection should not be an issue because
|
38
|
+
# query will be converted into LIKE statements word by
|
39
|
+
# word and special characters will be suppressed
|
40
|
+
def search_qd_conditions(query, fields=nil)
|
41
|
+
return nil if query.blank?
|
42
|
+
fields ||= @searchable_columns
|
43
|
+
|
44
|
+
# suppress special characters
|
45
|
+
search_qd_rm_special_chars(query)
|
46
|
+
|
47
|
+
# split the search string by spaces
|
48
|
+
words = query.split(' ')
|
49
|
+
|
50
|
+
or_statements = []
|
51
|
+
for word in words
|
52
|
+
like_statements = []
|
53
|
+
for column in fields
|
54
|
+
like_statements << "#{column} LIKE '%#{word.downcase}%'"
|
55
|
+
end
|
56
|
+
or_statements << ' ('+like_statements.join(' OR ')+') '
|
57
|
+
end
|
58
|
+
or_statements.join(' AND ')
|
59
|
+
end
|
60
|
+
|
61
|
+
# German umlauts are the only special characters
|
62
|
+
# which won't be removed from the search query
|
63
|
+
def search_qd_rm_special_chars(query)
|
64
|
+
query.gsub(/[^0-9a-zA-Z\säüöÜÄÖ]/, '')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/search_qd.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "search_qd/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "search_qd"
|
7
|
+
s.version = SearchQd::VERSION
|
8
|
+
s.authors = ["kgalli"]
|
9
|
+
s.email = ["mail@kgalli.de"]
|
10
|
+
s.homepage = "https://github.com/kgalli/search_qd"
|
11
|
+
s.summary = %q{Quick and dirty fulltext search for an ActiveRecord model.}
|
12
|
+
s.description = %q{SearchQd provides a search method for string/text columns of an ActiveRecord model. The search itself is handled via simple SQL LIKE statements. That is why it is called quick and dirty.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "search_qd"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "sqlite3"
|
23
|
+
|
24
|
+
s.add_dependency "activerecord"
|
25
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper.rb'
|
3
|
+
|
4
|
+
# blog class represents active record class
|
5
|
+
# which should be extended with functionality
|
6
|
+
# provided by SearchQd module
|
7
|
+
ActiveRecord::Base.send(:include, SearchQd)
|
8
|
+
class Blog < ActiveRecord::Base
|
9
|
+
search_qd_columns :title, :content
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe SearchQd do
|
14
|
+
describe ".search_qd" do
|
15
|
+
before do
|
16
|
+
Blog.all.each do |blog|
|
17
|
+
blog.delete
|
18
|
+
end
|
19
|
+
@blog1 = Blog.create title: "Find important blog using test as query", content: "This is some test text."
|
20
|
+
@blog2 = Blog.create title: "Do not find me", content: "This blog should not been found."
|
21
|
+
@blog3 = Blog.create title: "Second test", content: "Not very important."
|
22
|
+
@blog4 = Blog.create title: "Not important", content: "This is only some test content."
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should find all blogs where title or content include test" do
|
27
|
+
Blog.search_qd("test").should eq([@blog1, @blog3, @blog4])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should find all blogs where title or content include blog" do
|
31
|
+
Blog.search_qd("blog").should eq([@blog1, @blog2])
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should find all blogs where title includes test" do
|
35
|
+
Blog.search_qd("test", ["title"]).should eq([@blog1, @blog3])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should find all blogs where content includes important" do
|
39
|
+
Blog.search_qd("important", ["content"]).should eq([@blog3])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should find all blogs where title or content include not important" do
|
43
|
+
Blog.search_qd("not important").should eq([@blog3, @blog4])
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".search_qd_conditions" do
|
49
|
+
it "creates SQL LIKE statement for search_qd_columns" do
|
50
|
+
search_string = "Test"
|
51
|
+
expected_output = " (title LIKE '%test%' OR content LIKE '%test%') "
|
52
|
+
Blog.send("search_qd_conditions", search_string).should eq expected_output
|
53
|
+
end
|
54
|
+
|
55
|
+
it "creates SQL LIKE statement for dynamic fields" do
|
56
|
+
search_string = "Test"
|
57
|
+
search_fields = %w{ description title }
|
58
|
+
expected_output = " (description LIKE '%test%' OR title LIKE '%test%') "
|
59
|
+
Blog.send("search_qd_conditions", search_string, search_fields).should eq expected_output
|
60
|
+
end
|
61
|
+
|
62
|
+
it "creates multiple SQL LIKE statements combined with AND for each word in search string" do
|
63
|
+
search_string = "Test SearchQd"
|
64
|
+
expected_output_word1 = " (title LIKE '%test%' OR content LIKE '%test%') "
|
65
|
+
expected_output_word2 = " (title LIKE '%searchqd%' OR content LIKE '%searchqd%') "
|
66
|
+
expected_output = [ expected_output_word1, expected_output_word2 ].join(" AND ")
|
67
|
+
Blog.send("search_qd_conditions", search_string).should eq expected_output
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ".search_qd_rm_special_chars" do
|
72
|
+
it "removes special characters" do
|
73
|
+
special_chars = "§$%&/(){}#.,´`!,;@"
|
74
|
+
Blog.send("search_qd_rm_special_chars", special_chars).should eq('')
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not remove word characters" do
|
78
|
+
word_character = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
79
|
+
Blog.send("search_qd_rm_special_chars", word_character).should eq(word_character)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "does not remove numbers" do
|
83
|
+
numbers = "1234567890"
|
84
|
+
Blog.send("search_qd_rm_special_chars", numbers).should eq(numbers)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not remove German umlauts" do
|
88
|
+
german_umlauts = "äÄüÜöÖ"
|
89
|
+
Blog.send("search_qd_rm_special_chars", german_umlauts).should eq(german_umlauts)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: search_qd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- kgalli
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70355747987560 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70355747987560
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sqlite3
|
27
|
+
requirement: &70355747987140 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70355747987140
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: activerecord
|
38
|
+
requirement: &70355747986720 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70355747986720
|
47
|
+
description: SearchQd provides a search method for string/text columns of an ActiveRecord
|
48
|
+
model. The search itself is handled via simple SQL LIKE statements. That is why
|
49
|
+
it is called quick and dirty.
|
50
|
+
email:
|
51
|
+
- mail@kgalli.de
|
52
|
+
executables: []
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- .rspec
|
58
|
+
- CHANGELOG.md
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- lib/search_qd.rb
|
64
|
+
- lib/search_qd/rails.rb
|
65
|
+
- lib/search_qd/version.rb
|
66
|
+
- search_qd.gemspec
|
67
|
+
- spec/db_config.yml.example
|
68
|
+
- spec/search_qd_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
homepage: https://github.com/kgalli/search_qd
|
71
|
+
licenses: []
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project: search_qd
|
90
|
+
rubygems_version: 1.8.6
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Quick and dirty fulltext search for an ActiveRecord model.
|
94
|
+
test_files:
|
95
|
+
- spec/db_config.yml.example
|
96
|
+
- spec/search_qd_spec.rb
|
97
|
+
- spec/spec_helper.rb
|