poor_man_search 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +6 -0
- data/lib/poor_man_search.rb +3 -0
- data/lib/poor_man_search/criteria.rb +60 -0
- data/lib/poor_man_search/searchable.rb +105 -0
- data/lib/poor_man_search/version.rb +3 -0
- data/poor_man_search.gemspec +31 -0
- data/spec/criteria_spec.rb +195 -0
- data/spec/poor_man_search_spec.rb +7 -0
- data/spec/searchable_spec.rb +119 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/database_cleaner.rb +15 -0
- data/spec/support/test_models.rb +35 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c93f4fed76ff17adb3df0be80611704d59aa29be
|
4
|
+
data.tar.gz: 8fc463375bbd69c6ef4f151cb25ff5608e89a9a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6a34f887dff07995ac5823e2637692f3bdaa01f366a99589ba94568d866c95d09c8c57efa383d0315dee45574cf75b5bd4e8ee9d34cd748fac8f231090324787
|
7
|
+
data.tar.gz: 49541c24470958b6a1c3c1984715600067491e0432269243710d1300fe64860a69a3462b5f5e26caf1edc6caf2a650dba9f919dc7a7213834ad041f2c5ea4dcd
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
|
19
|
+
*#*
|
20
|
+
.DS_Store
|
21
|
+
.rvmrc
|
22
|
+
.ruby-version
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Yasuhiko Maeda
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# PoorManSearch
|
2
|
+
|
3
|
+
This module implement **Poor Man's Search Engin** to a active-record object.
|
4
|
+
> Poor Man's Search Engin is a SQL anti pattern that using LIKE with wildcards search..
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'poor_man_search'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### Example
|
19
|
+
|
20
|
+
A master table : users
|
21
|
+
|
22
|
+
| email | name | rank | registered_at |
|
23
|
+
|------------------|--------------|-----|-------------|
|
24
|
+
| taro@yamada.example.com | 山田太郎 | 1 | 2013-12-31 23:21:11 |
|
25
|
+
| hana@yamada.example.com | 山田華 | 3 | 2014-2-28 3:15:00 |
|
26
|
+
| chad@miller.example.com | Chad Miller | 1 | 2014-2-28 16:23:15 |
|
27
|
+
|
28
|
+
|
29
|
+
Configure like this.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class User < ActiveRecord::Base
|
33
|
+
extend PoorManSearch::Searchable
|
34
|
+
string_search :name, :email
|
35
|
+
number_search :rank
|
36
|
+
time_search :registered_at
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
### Search!
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
User.search "yamada"
|
44
|
+
```
|
45
|
+
|
46
|
+
| email | name | rank | registered_at |
|
47
|
+
|------------------|--------------|-----|-------------|
|
48
|
+
| taro@yamada.example.com | 山田太郎 | 1 | 2013-12-31 23:21:11 |
|
49
|
+
| hana@yamada.example.com | 山田華 | 3 | 2014-2-28 3:15:00 |
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
User.search "yamada 太郎"
|
55
|
+
```
|
56
|
+
|
57
|
+
| email | name | rank | registered_at |
|
58
|
+
|------------------|--------------|-----|-------------|
|
59
|
+
| taro@yamada.example.com | 山田太郎 | 1 | 2013-12-31 23:21:11 |
|
60
|
+
|
61
|
+
---
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
User.search "1"
|
65
|
+
```
|
66
|
+
|
67
|
+
| email | name | rank | registered_at |
|
68
|
+
|------------------|--------------|-----|-------------|
|
69
|
+
| taro@yamada.example.com | 山田太郎 | 1 | 2013-12-31 23:21:11 |
|
70
|
+
| chad@miller.example.com | Chad Miller | 1 | 2014-2-28 16:23:15 |
|
71
|
+
|
72
|
+
---
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
User.search "2/28"
|
76
|
+
```
|
77
|
+
|
78
|
+
| email | name | rank | registered_at |
|
79
|
+
|------------------|--------------|-----|-------------|
|
80
|
+
| hana@yamada.example.com | 山田華 | 3 | 2014-2-28 3:15:00 |
|
81
|
+
| chad@miller.example.com | Chad Miller | 1 | 2014-2-28 16:23:15 |
|
82
|
+
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
1. Fork it
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module PoorManSearch
|
2
|
+
class Criteria
|
3
|
+
attr_accessor :strings, :stringables, :numbers, :times, :time_range
|
4
|
+
|
5
|
+
@s = nil
|
6
|
+
|
7
|
+
def initialize irregularity = nil
|
8
|
+
parse irregularity
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def parse irregularity
|
14
|
+
self.strings = []
|
15
|
+
self.stringables = []
|
16
|
+
self.numbers = []
|
17
|
+
self.times = []
|
18
|
+
self.time_range = []
|
19
|
+
|
20
|
+
words = irregularity.to_s.gsub / /, " "
|
21
|
+
words.split(" ").each do |word|
|
22
|
+
num = number_parse word
|
23
|
+
time = time_parse word
|
24
|
+
|
25
|
+
self.strings << word unless num || time
|
26
|
+
self.numbers << num if num
|
27
|
+
self.times << time if time
|
28
|
+
end
|
29
|
+
|
30
|
+
self.time_range = [self.times.min, self.times.max] if self.times.count >= 2
|
31
|
+
end
|
32
|
+
|
33
|
+
DATE_FORMATS = {:date_hour_minute => [/^\d+(\/)\d+(-)\d+(:)\d+$/, '%m/%d-%H:%M'],
|
34
|
+
:date_hour => [/^\d+(\/)\d+(-)\d+$/, '%m/%d-%H'],
|
35
|
+
:date => [/^\d+(\/)\d+$/, '%m/%d'],
|
36
|
+
:time => [/^\d+(:)\d+$/, '%H:%M']}
|
37
|
+
|
38
|
+
def time_parse ss
|
39
|
+
date = parse_format(ss, DATE_FORMATS[:date_hour_minute])
|
40
|
+
date ||= parse_format(ss, DATE_FORMATS[:date_hour])
|
41
|
+
date ||= parse_format(ss, DATE_FORMATS[:date])
|
42
|
+
date ||= parse_format(ss, DATE_FORMATS[:time])
|
43
|
+
return date
|
44
|
+
rescue Exception
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_format(ss, format)
|
49
|
+
return nil unless ss=~ format[0]
|
50
|
+
return Time.strptime(ss, format[1])
|
51
|
+
rescue Exception
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def number_parse s
|
56
|
+
return nil unless s =~ /^(|-)\d+(|\.(|\d+))$/
|
57
|
+
return BigDecimal.new s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module PoorManSearch
|
2
|
+
module Searchable
|
3
|
+
def string_search *fields
|
4
|
+
@string_fields = fields
|
5
|
+
end
|
6
|
+
|
7
|
+
def number_search *fields
|
8
|
+
@number_fields = fields
|
9
|
+
end
|
10
|
+
|
11
|
+
def time_search *fields
|
12
|
+
@time_fields = fields
|
13
|
+
end
|
14
|
+
|
15
|
+
def time_range *fields
|
16
|
+
@time_fields = fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def search key_words_string
|
20
|
+
criteria = Criteria.new key_words_string
|
21
|
+
t = self.arel_table
|
22
|
+
|
23
|
+
search_scope = scoped
|
24
|
+
clauses = string_clauses(t, criteria)
|
25
|
+
clauses << number_clause(t, criteria)
|
26
|
+
clauses << time_clause(t, criteria)
|
27
|
+
|
28
|
+
clauses.compact.each{|clause|
|
29
|
+
search_scope = search_scope.where(clause)
|
30
|
+
}
|
31
|
+
search_scope
|
32
|
+
end
|
33
|
+
|
34
|
+
def associative_search key_words_string, *associations
|
35
|
+
ids = search(key_words_string).select("#{self.table_name}.id")
|
36
|
+
ids += associations.collect{|as|
|
37
|
+
scoped.includes(as).merge(asociation_class(as).search(key_words_string)).select("#{self.table_name}.id")
|
38
|
+
}.flatten
|
39
|
+
where(:id => ids.uniq)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def asociation_class as_sym
|
45
|
+
_as = as_sym
|
46
|
+
while(_as.is_a? Hash) do
|
47
|
+
_as = _as.values.first
|
48
|
+
end
|
49
|
+
_as.to_s.singularize.classify.constantize
|
50
|
+
end
|
51
|
+
|
52
|
+
def string_clauses(t, criteria)
|
53
|
+
return [] if @string_fields.blank?
|
54
|
+
criteria.strings.collect do |word|
|
55
|
+
v = "%#{word}%"
|
56
|
+
clause = nil
|
57
|
+
|
58
|
+
@string_fields.collect{|f|
|
59
|
+
t[f].matches(v)
|
60
|
+
}.each{|t_node|
|
61
|
+
clause = clause.or(t_node) rescue t_node
|
62
|
+
}
|
63
|
+
clause
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def number_clause(t, criteria)
|
68
|
+
return nil if @number_fields.blank?
|
69
|
+
clause = nil
|
70
|
+
criteria.numbers.collect do |value|
|
71
|
+
@number_fields.collect{|f|
|
72
|
+
t[f].eq(value)
|
73
|
+
}.each{|t_node|
|
74
|
+
clause = clause.or(t_node) rescue t_node
|
75
|
+
}
|
76
|
+
end
|
77
|
+
clause
|
78
|
+
end
|
79
|
+
|
80
|
+
def time_clause(t, criteria)
|
81
|
+
return nil if @time_fields.blank?
|
82
|
+
clause = nil
|
83
|
+
criteria.times.each do |value|
|
84
|
+
@time_fields.each{|f|
|
85
|
+
find_by_time(t, value).each{|t_node|
|
86
|
+
clause = clause.or(t_node) rescue t_node
|
87
|
+
}
|
88
|
+
}
|
89
|
+
end
|
90
|
+
clause
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_by_time(table, time)
|
94
|
+
return fetch_from_time_range(table, time, time+59.seconds) if (time.hour > 0 && time.min > 0)
|
95
|
+
return fetch_from_time_range(table, time.beginning_of_hour, time.end_of_hour) if (time.hour > 0)
|
96
|
+
return fetch_from_time_range(table, time.beginning_of_day, time.end_of_day)
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetch_from_time_range(table, start_time, end_time)
|
100
|
+
@time_fields.collect{|f|
|
101
|
+
table[f].gteq(start_time).and(table[f].lteq(end_time))
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'poor_man_search/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "poor_man_search"
|
8
|
+
spec.version = PoorManSearch::VERSION
|
9
|
+
spec.authors = ["Yasuhiko Maeda"]
|
10
|
+
spec.email = ["y.maeda@dongoon.jp"]
|
11
|
+
spec.description = %q{Poor man's search engine module for Rails3 (Arel)}
|
12
|
+
spec.summary = %q{Poor man's search engine module for Rails3 (Arel)}
|
13
|
+
spec.homepage = "https://github.com/dongoon/poor_man_search"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "simplecov"
|
26
|
+
spec.add_development_dependency "sqlite3-ruby"
|
27
|
+
spec.add_development_dependency "database_cleaner"
|
28
|
+
|
29
|
+
spec.add_runtime_dependency "activerecord", "~> 3.2", ">= 3.0.0"
|
30
|
+
spec.add_runtime_dependency "arel", ">= 3.0.0"
|
31
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PoorManSearch::Criteria do
|
4
|
+
describe "::new" do
|
5
|
+
it "should receive single params" do
|
6
|
+
proc{PoorManSearch::Criteria.new("abc")}.should_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should receive single params nilable" do
|
10
|
+
proc{PoorManSearch::Criteria.new}.should_not raise_error
|
11
|
+
proc{PoorManSearch::Criteria.new(nil)}.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not receive multi params" do
|
15
|
+
proc{PoorManSearch::Criteria.new("a", "b")}.should raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "parse" do
|
20
|
+
|
21
|
+
describe "time_parse" do
|
22
|
+
let(:c){PoorManSearch::Criteria.new}
|
23
|
+
context "time parsable string" do
|
24
|
+
it "should return Time parsed object" do
|
25
|
+
[
|
26
|
+
"2/1",
|
27
|
+
"12/31-12:54",
|
28
|
+
"11:30",
|
29
|
+
].each{|s|
|
30
|
+
c.send(:time_parse, s).should == Time.parse(s)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "NOT time parsable string" do
|
36
|
+
it "should return nil" do
|
37
|
+
[
|
38
|
+
"hoge",
|
39
|
+
"1231",
|
40
|
+
"13/9",
|
41
|
+
].each{|s|
|
42
|
+
c.send(:time_parse, s).should be_nil
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "number_parse" do
|
49
|
+
let(:c){PoorManSearch::Criteria.new}
|
50
|
+
|
51
|
+
context "number parsable string" do
|
52
|
+
it "should return BigDecimal object" do
|
53
|
+
[
|
54
|
+
"1",
|
55
|
+
"134",
|
56
|
+
"12.001",
|
57
|
+
"11.",
|
58
|
+
"-1.5",
|
59
|
+
].each{|s|
|
60
|
+
c.send(:number_parse, s).should == BigDecimal.new(s)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "NOT number parsable string" do
|
66
|
+
it "should return nil" do
|
67
|
+
[
|
68
|
+
"hoge",
|
69
|
+
"12/31",
|
70
|
+
"1.3.9",
|
71
|
+
"1..3",
|
72
|
+
].each{|s|
|
73
|
+
c.send(:number_parse, s).should be_nil
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "empty params" do
|
80
|
+
let(:criteria){
|
81
|
+
PoorManSearch::Criteria.new(@search_string)
|
82
|
+
}
|
83
|
+
|
84
|
+
before {
|
85
|
+
criteria.strings = %w(hoge fuga)
|
86
|
+
criteria.stringables = %w(1/2 2/2)
|
87
|
+
criteria.numbers = [1, 2, 3]
|
88
|
+
criteria.times = [Time.now, 1.day.ago, 2.day.since]
|
89
|
+
criteria.time_range = [1.day.ago, Date.today]
|
90
|
+
}
|
91
|
+
it "should not raise error" do
|
92
|
+
proc{
|
93
|
+
criteria.send :parse, nil
|
94
|
+
}.should_not raise_error
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should make properties to nil" do
|
98
|
+
criteria.strings.count.should == 2
|
99
|
+
criteria.send :parse, nil
|
100
|
+
criteria.strings.should == []
|
101
|
+
criteria.stringables.should == []
|
102
|
+
criteria.times.should == []
|
103
|
+
criteria.time_range.should == []
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "strings" do
|
108
|
+
context "single word" do
|
109
|
+
it "should be [sing_word]" do
|
110
|
+
criteria = PoorManSearch::Criteria.new "あれ"
|
111
|
+
criteria.strings.count.should == 1
|
112
|
+
criteria.strings.should == ["あれ"]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "multi words" do
|
117
|
+
it "should be [word1, word2]" do
|
118
|
+
criteria = PoorManSearch::Criteria.new " あれ hoge\tどれ\nmore a "
|
119
|
+
criteria.strings.count.should == 5
|
120
|
+
criteria.strings.should == ["あれ", "hoge", "どれ", "more", "a"]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with word be parsable other type" do
|
125
|
+
it "should be [word(!number & !time)..]" do
|
126
|
+
criteria = PoorManSearch::Criteria.new "あれ 1/2 どれ 123"
|
127
|
+
criteria.strings.count.should == 2
|
128
|
+
criteria.strings.should == ["あれ", "どれ"]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "numbers" do
|
134
|
+
context "with numberable words" do
|
135
|
+
it "should be [number..]" do
|
136
|
+
criteria = PoorManSearch::Criteria.new "hoge 123 1.02 fuga 2"
|
137
|
+
criteria.numbers.count.should == 3
|
138
|
+
criteria.numbers.should == [
|
139
|
+
BigDecimal.new("123"),
|
140
|
+
BigDecimal.new("1.02"),
|
141
|
+
BigDecimal.new("2")
|
142
|
+
]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "without numberable word" do
|
147
|
+
it "should be empty array" do
|
148
|
+
criteria = PoorManSearch::Criteria.new "hoge 123/3 1.r fuga "
|
149
|
+
criteria.numbers.should == []
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "times" do
|
155
|
+
context "with timable words" do
|
156
|
+
it "should be [time..]" do
|
157
|
+
criteria = PoorManSearch::Criteria.new "hoge 1/2 2/10-10:13 fuga 2/2"
|
158
|
+
criteria.times.count.should == 3
|
159
|
+
criteria.times.should == [
|
160
|
+
Time.parse("1/2"),
|
161
|
+
Time.parse("2/10-10:13"),
|
162
|
+
Time.parse("2/2")
|
163
|
+
]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "without timable word" do
|
168
|
+
it "should be empty array" do
|
169
|
+
criteria = PoorManSearch::Criteria.new "hoge 1233 1.r fuga "
|
170
|
+
criteria.times.should == []
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "time_range" do
|
176
|
+
context "with two or more timable words" do
|
177
|
+
it "should be [min, max]" do
|
178
|
+
criteria = PoorManSearch::Criteria.new "2/2 1/10 hoge 1/15"
|
179
|
+
criteria.time_range.count.should == 2
|
180
|
+
criteria.time_range.should == [
|
181
|
+
Time.parse("1/10"),
|
182
|
+
Time.parse("2/2")
|
183
|
+
]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "with one or less timable words" do
|
188
|
+
it "should be empty array" do
|
189
|
+
criteria = PoorManSearch::Criteria.new "2/2"
|
190
|
+
criteria.time_range.count.should == 0
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# see test_models.rb
|
4
|
+
class User
|
5
|
+
extend PoorManSearch::Searchable
|
6
|
+
string_search :name, :email
|
7
|
+
number_search :rank
|
8
|
+
time_search :registered_at
|
9
|
+
end
|
10
|
+
|
11
|
+
class Comment
|
12
|
+
extend PoorManSearch::Searchable
|
13
|
+
string_search :said
|
14
|
+
number_search :count
|
15
|
+
end
|
16
|
+
|
17
|
+
describe PoorManSearch::Searchable do
|
18
|
+
let(:user1){ User.create(email: "taro@yamada.example.com",
|
19
|
+
name: "山田太郎",
|
20
|
+
rank: 1,
|
21
|
+
registered_at: Time.parse('2013-12-31 23:21:11')) }
|
22
|
+
|
23
|
+
let(:user2){ User.create(email: "hana@yamada.example.com",
|
24
|
+
name: "山田華",
|
25
|
+
rank: 3,
|
26
|
+
registered_at: Time.parse('2014-2-28 3:15:00')) }
|
27
|
+
|
28
|
+
let(:user3){ User.create(email: "chad@miller.example.com",
|
29
|
+
name: "Chad Miller",
|
30
|
+
rank: 1,
|
31
|
+
registered_at: Time.parse('2014-2-28 16:23:15')) }
|
32
|
+
|
33
|
+
let(:users){ [user1, user2, user3] }
|
34
|
+
before{ users }
|
35
|
+
|
36
|
+
describe "::search" do
|
37
|
+
|
38
|
+
subject{ User.search search_string }
|
39
|
+
|
40
|
+
shared_examples_for "User#search" do
|
41
|
+
it "should get records match search string" do
|
42
|
+
subject.count.should == expected_hits.count
|
43
|
+
expected_hits.each{|user| subject.map(&:email).should be_include user.email }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "string field search" do
|
48
|
+
context "single word" do
|
49
|
+
let(:search_string){ "yamada" }
|
50
|
+
let(:expected_hits){ [user1, user2] }
|
51
|
+
it_behaves_like "User#search"
|
52
|
+
end
|
53
|
+
|
54
|
+
context "multiple words" do
|
55
|
+
let(:search_string){ "yamada 太郎" }
|
56
|
+
let(:expected_hits){ [user1] }
|
57
|
+
it_behaves_like "User#search"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "number field search" do
|
62
|
+
let(:search_string){ "1" }
|
63
|
+
let(:expected_hits){ [user1, user3] }
|
64
|
+
it_behaves_like "User#search"
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "time field search" do
|
68
|
+
context "date" do
|
69
|
+
let(:search_string){ "2/28" }
|
70
|
+
let(:expected_hits){ [user2, user3] }
|
71
|
+
it_behaves_like "User#search"
|
72
|
+
end
|
73
|
+
|
74
|
+
context "datetime" do
|
75
|
+
let(:search_string){ "2/28-3:15" }
|
76
|
+
let(:expected_hits){ [user2] }
|
77
|
+
it_behaves_like "User#search"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "types mix" do
|
82
|
+
let(:search_string){ "example.com 3" }
|
83
|
+
let(:expected_hits){ [user2] }
|
84
|
+
it_behaves_like "User#search"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "::associative_search" do
|
89
|
+
before{
|
90
|
+
user1.say "どうもどうも"
|
91
|
+
user2.say "あら遅いじゃない"
|
92
|
+
user3.say "now?"
|
93
|
+
user1.say "なんだって!?"
|
94
|
+
user2.say "どうどう"
|
95
|
+
user1.say "馬?"
|
96
|
+
}
|
97
|
+
|
98
|
+
subject{ User.associative_search search_string, :comments }
|
99
|
+
|
100
|
+
shared_examples_for "User#associative_search" do
|
101
|
+
it "should get records match search string" do
|
102
|
+
subject.count.should == expected_hits.count
|
103
|
+
expected_hits.each{|user| subject.map(&:email).should be_include user.email }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "associate entity search case1" do
|
108
|
+
let(:search_string){ "どう" }
|
109
|
+
let(:expected_hits){ [user1, user2] }
|
110
|
+
it_behaves_like "User#associative_search"
|
111
|
+
end
|
112
|
+
|
113
|
+
context "associate entity search case2" do
|
114
|
+
let(:search_string){ "?" }
|
115
|
+
let(:expected_hits){ [user1, user3] }
|
116
|
+
it_behaves_like "User#associative_search"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'rspec'
|
3
|
+
require 'poor_man_search'
|
4
|
+
|
5
|
+
if ENV['COVERAGE']
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter '/spec/'
|
9
|
+
add_filter '/vendor/'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# load spec/support/**/*.rb
|
14
|
+
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each {|f| require f}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'database_cleaner'
|
2
|
+
|
3
|
+
DatabaseCleaner[:active_record].strategy = :transaction if defined? ActiveRecord
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.before :suite do
|
7
|
+
DatabaseCleaner.clean_with :truncation
|
8
|
+
end
|
9
|
+
config.before :each do
|
10
|
+
DatabaseCleaner.start
|
11
|
+
end
|
12
|
+
config.after :each do
|
13
|
+
DatabaseCleaner.clean
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ':memory:')
|
3
|
+
|
4
|
+
class CreateTestTables < ActiveRecord::Migration
|
5
|
+
def self.up
|
6
|
+
create_table :users do |t|
|
7
|
+
t.string :email
|
8
|
+
t.string :name
|
9
|
+
t.integer :rank
|
10
|
+
t.datetime :registered_at
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :comments do |t|
|
14
|
+
t.integer :user_id
|
15
|
+
t.integer :count
|
16
|
+
t.string :said
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Migration.verbose = false
|
22
|
+
CreateTestTables.up
|
23
|
+
|
24
|
+
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
has_many :comments
|
27
|
+
|
28
|
+
def say comment
|
29
|
+
comments.create(count: comments.count + 1, said: comment)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Comment < ActiveRecord::Base
|
34
|
+
belongs_to :user
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: poor_man_search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yasuhiko Maeda
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3-ruby
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: database_cleaner
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.2'
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 3.0.0
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ~>
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '3.2'
|
114
|
+
- - '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 3.0.0
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: arel
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 3.0.0
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 3.0.0
|
131
|
+
description: Poor man's search engine module for Rails3 (Arel)
|
132
|
+
email:
|
133
|
+
- y.maeda@dongoon.jp
|
134
|
+
executables: []
|
135
|
+
extensions: []
|
136
|
+
extra_rdoc_files: []
|
137
|
+
files:
|
138
|
+
- .gitignore
|
139
|
+
- .rspec
|
140
|
+
- .travis.yml
|
141
|
+
- Gemfile
|
142
|
+
- LICENSE.txt
|
143
|
+
- README.md
|
144
|
+
- Rakefile
|
145
|
+
- lib/poor_man_search.rb
|
146
|
+
- lib/poor_man_search/criteria.rb
|
147
|
+
- lib/poor_man_search/searchable.rb
|
148
|
+
- lib/poor_man_search/version.rb
|
149
|
+
- poor_man_search.gemspec
|
150
|
+
- spec/criteria_spec.rb
|
151
|
+
- spec/poor_man_search_spec.rb
|
152
|
+
- spec/searchable_spec.rb
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
- spec/support/database_cleaner.rb
|
155
|
+
- spec/support/test_models.rb
|
156
|
+
homepage: https://github.com/dongoon/poor_man_search
|
157
|
+
licenses:
|
158
|
+
- MIT
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.2.2
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: Poor man's search engine module for Rails3 (Arel)
|
180
|
+
test_files:
|
181
|
+
- spec/criteria_spec.rb
|
182
|
+
- spec/poor_man_search_spec.rb
|
183
|
+
- spec/searchable_spec.rb
|
184
|
+
- spec/spec_helper.rb
|
185
|
+
- spec/support/database_cleaner.rb
|
186
|
+
- spec/support/test_models.rb
|