lunar 0.2.3 → 0.3.0
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 +1 -0
- data/README.md +16 -0
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/lunar.rb +16 -6
- data/lib/lunar/fuzzy_word.rb +7 -0
- data/lib/lunar/index.rb +52 -2
- data/lib/lunar/result_set.rb +24 -7
- data/lib/lunar/search.rb +33 -11
- data/lib/lunar/sets.rb +23 -4
- data/lunar.gemspec +10 -2
- data/test/helper.rb +1 -0
- data/test/test_lunar_fuzzy.rb +118 -0
- data/test/test_lunar_fuzzy_word.rb +14 -0
- data/test/test_lunar_index.rb +5 -12
- data/test/test_lunar_search.rb +97 -0
- metadata +20 -3
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -40,6 +40,14 @@ Examples
|
|
40
40
|
i.attr :name, name
|
41
41
|
i.attr :description, description
|
42
42
|
end
|
43
|
+
|
44
|
+
Lunar::Index.create Item do |i|
|
45
|
+
i.key id
|
46
|
+
i.fuzzy :name, name # this has a 100 character limit on name
|
47
|
+
# for performance reasons
|
48
|
+
i.integer :cost, cost
|
49
|
+
i.float :voting_quotient, voting_quotient
|
50
|
+
end
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
@@ -49,6 +57,14 @@ Examples
|
|
49
57
|
|
50
58
|
# Or opt to filter by field
|
51
59
|
Lunar.search(Item, :name => "iphone", :description => "mobile")
|
60
|
+
|
61
|
+
# For fuzzy declared fields you can currently only search
|
62
|
+
# using a fuzzy strategy exclusively, e.g.
|
63
|
+
Lunar.search(Item, :fuzzy => { :name => "i" })
|
64
|
+
# i, ip, iph, ipho, iphone, 3, 3g, 3gs all would match 'iPhone 3Gs'
|
65
|
+
|
66
|
+
# For integer / float types, you can do range searches on them e.g.
|
67
|
+
Lunar.search(Item, :cost => 300..500, :voting_quotient => 10..20)
|
52
68
|
|
53
69
|
# Or using the pagination gem with this:
|
54
70
|
@items = Lunar.search(Item, "iphone")
|
data/Rakefile
CHANGED
@@ -11,6 +11,7 @@ begin
|
|
11
11
|
gem.homepage = "http://github.com/cyx/lunar"
|
12
12
|
gem.authors = ["Cyril David"]
|
13
13
|
gem.add_development_dependency "contest"
|
14
|
+
gem.add_development_dependency "mocha"
|
14
15
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
16
|
end
|
16
17
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/lunar.rb
CHANGED
@@ -2,12 +2,22 @@ require 'base64'
|
|
2
2
|
require File.join(File.dirname(__FILE__), '..', 'vendor', 'nest', 'nest')
|
3
3
|
|
4
4
|
module Lunar
|
5
|
-
autoload :Scoring,
|
6
|
-
autoload :Index,
|
7
|
-
autoload :Words,
|
8
|
-
autoload :Search,
|
9
|
-
autoload :Sets,
|
10
|
-
autoload :
|
5
|
+
autoload :Scoring, 'lunar/scoring'
|
6
|
+
autoload :Index, 'lunar/index'
|
7
|
+
autoload :Words, 'lunar/words'
|
8
|
+
autoload :Search, 'lunar/search'
|
9
|
+
autoload :Sets, 'lunar/sets'
|
10
|
+
autoload :SortedResultSet, 'lunar/result_set'
|
11
|
+
autoload :UnsortedResultSet, 'lunar/result_set'
|
12
|
+
autoload :FuzzyWord, 'lunar/fuzzy_word'
|
13
|
+
|
14
|
+
def self.ttl
|
15
|
+
@ttl ||= 30
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.ttl=(ttl)
|
19
|
+
@ttl = ttl
|
20
|
+
end
|
11
21
|
|
12
22
|
def self.redis(connection = defined?(Ohm) ? Ohm.redis : nil)
|
13
23
|
@connection ||= connection
|
data/lib/lunar/index.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module Lunar
|
2
2
|
class Index
|
3
|
+
FUZZY_MAX_LENGTH = 100
|
4
|
+
|
5
|
+
FuzzyFieldTooLong = Class.new(StandardError)
|
6
|
+
|
3
7
|
attr :ns
|
4
8
|
|
5
9
|
def self.create(prefix)
|
@@ -15,6 +19,7 @@ module Lunar
|
|
15
19
|
@attrs = {}
|
16
20
|
@redis = (redis || Lunar.redis)
|
17
21
|
@numeric_attrs = {}
|
22
|
+
@fuzzy_attrs = {}
|
18
23
|
end
|
19
24
|
|
20
25
|
def key(key = nil)
|
@@ -38,8 +43,14 @@ module Lunar
|
|
38
43
|
alias :integer :numeric_attr
|
39
44
|
alias :float :numeric_attr
|
40
45
|
|
46
|
+
def fuzzy(field, value = nil)
|
47
|
+
@fuzzy_attrs[field.to_sym] = value if value
|
48
|
+
@fuzzy_attrs
|
49
|
+
end
|
50
|
+
|
41
51
|
def index
|
42
52
|
index_str_attrs
|
53
|
+
index_fuzzy_attrs
|
43
54
|
index_numeric_attrs
|
44
55
|
|
45
56
|
return self
|
@@ -53,7 +64,17 @@ module Lunar
|
|
53
64
|
words.each do |w|
|
54
65
|
@redis.zrem @ns[field][Lunar.encode(w)], key
|
55
66
|
end
|
56
|
-
@redis.del k
|
67
|
+
@redis.del k
|
68
|
+
end
|
69
|
+
|
70
|
+
fuzzy_keys = @redis.keys @ns[:Fuzzy][key]['*']
|
71
|
+
fuzzy_keys.each do |k|
|
72
|
+
field = k.gsub("#{ @ns[:Fuzzy][key] }:", '')
|
73
|
+
words = @redis.smembers(k)
|
74
|
+
words.each do |w|
|
75
|
+
remove_fuzzy_values field
|
76
|
+
end
|
77
|
+
@redis.del k
|
57
78
|
end
|
58
79
|
end
|
59
80
|
|
@@ -76,11 +97,40 @@ module Lunar
|
|
76
97
|
end
|
77
98
|
end
|
78
99
|
|
100
|
+
def index_fuzzy_attrs
|
101
|
+
@fuzzy_attrs.each do |field, value|
|
102
|
+
if value.to_s.length > FUZZY_MAX_LENGTH
|
103
|
+
raise FuzzyFieldTooLong,
|
104
|
+
"#{field} has a value #{value} exceeding #{FUZZY_MAX_LENGTH} chars"
|
105
|
+
end
|
106
|
+
|
107
|
+
words = Words.new(value).uniq
|
108
|
+
words.each do |word|
|
109
|
+
FuzzyWord.new(word).partials.each do |partial|
|
110
|
+
@redis.sadd @ns[:Fuzzy][field][Lunar.encode(partial)], key
|
111
|
+
end
|
112
|
+
@redis.sadd @ns[:Fuzzy][key][field], word
|
113
|
+
end
|
114
|
+
|
115
|
+
remove_fuzzy_values field, words
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def remove_fuzzy_values(field, words = [])
|
120
|
+
unused_words = @redis.smembers(@ns[:Fuzzy][key][field]) - words
|
121
|
+
unused_words.each do |word|
|
122
|
+
FuzzyWord.new(word).partials.each do |partial|
|
123
|
+
next if words.grep(/^#{partial}/u).any?
|
124
|
+
@redis.srem @ns[:Fuzzy][field][Lunar.encode(partial)], key
|
125
|
+
end
|
126
|
+
@redis.srem @ns[:Fuzzy][key][field], word
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
79
130
|
def index_numeric_attrs
|
80
131
|
@numeric_attrs.each do |field, value|
|
81
132
|
@redis.zadd @ns[field], value, key
|
82
133
|
end
|
83
134
|
end
|
84
|
-
|
85
135
|
end
|
86
136
|
end
|
data/lib/lunar/result_set.rb
CHANGED
@@ -2,26 +2,43 @@ module Lunar
|
|
2
2
|
class ResultSet
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
-
attr :
|
5
|
+
attr :key
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@block
|
7
|
+
def initialize(key, &block)
|
8
|
+
@key = key
|
9
|
+
@block = block
|
10
10
|
end
|
11
11
|
|
12
12
|
def each(&block)
|
13
13
|
all.each(&block)
|
14
14
|
end
|
15
|
+
end
|
15
16
|
|
17
|
+
class SortedResultSet < ResultSet
|
16
18
|
def all(options = {})
|
17
19
|
start = Integer(options[:start] || 0)
|
18
|
-
|
20
|
+
limit = Integer(options[:limit] || 0)
|
21
|
+
finish = start + limit - 1
|
22
|
+
|
23
|
+
puts "Getting: #{key}, #{Lunar.redis.zrange(key, start, finish)}" if $GAME
|
24
|
+
Lunar.redis.zrevrange(key, start, finish).map(&@block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
Lunar.redis.zcard(key)
|
29
|
+
end
|
30
|
+
end
|
19
31
|
|
20
|
-
|
32
|
+
class UnsortedResultSet < ResultSet
|
33
|
+
def all(options = {})
|
34
|
+
start = Integer(options[:start] || 0)
|
35
|
+
limit = Integer(options[:limit] || 100)
|
36
|
+
|
37
|
+
Lunar.redis.sort(key, :limit => [start, limit]).map(&@block)
|
21
38
|
end
|
22
39
|
|
23
40
|
def size
|
24
|
-
Lunar.redis.
|
41
|
+
Lunar.redis.scard(key)
|
25
42
|
end
|
26
43
|
end
|
27
44
|
end
|
data/lib/lunar/search.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
module Lunar
|
2
2
|
class Search
|
3
|
-
attr :sets, :prefix, :search_identifier
|
3
|
+
attr :sets, :prefix, :search_identifier, :fuzzy_sets
|
4
4
|
|
5
5
|
def initialize(prefix, keywords)
|
6
6
|
if keywords.is_a?(Hash)
|
7
|
-
|
8
|
-
|
7
|
+
if fuzzy_hash = keywords.delete(:fuzzy)
|
8
|
+
@fuzzy_sets = fuzzy_hash.inject([]) { |a, (field, query)|
|
9
|
+
a | FuzzySets.new(prefix, query, field)
|
10
|
+
}
|
11
|
+
@search_identifier = fuzzy_hash.hash
|
12
|
+
else
|
13
|
+
@sets = keywords.inject([]) { |a, (field, query)|
|
14
|
+
a | Sets.new(prefix, query, field)
|
15
|
+
}
|
16
|
+
@search_identifier = keywords.hash
|
17
|
+
end
|
9
18
|
else
|
10
19
|
@sets = Sets.new(prefix, keywords)
|
11
20
|
@search_identifier = keywords.hash
|
@@ -18,15 +27,28 @@ module Lunar
|
|
18
27
|
end
|
19
28
|
|
20
29
|
def results(&block)
|
21
|
-
|
30
|
+
block ||= @finder
|
22
31
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
if sets
|
33
|
+
if sets.empty?
|
34
|
+
return []
|
35
|
+
else
|
36
|
+
if not Lunar.redis.exists(dist_key)
|
37
|
+
Lunar.redis.zunion dist_key, sets.size, *sets
|
38
|
+
Lunar.redis.expire dist_key, Lunar.ttl
|
39
|
+
end
|
40
|
+
SortedResultSet.new(dist_key, &block)
|
41
|
+
end
|
42
|
+
elsif fuzzy_sets
|
43
|
+
if fuzzy_sets.empty?
|
44
|
+
return []
|
45
|
+
else
|
46
|
+
if not Lunar.redis.exists(dist_key)
|
47
|
+
Lunar.redis.sunionstore dist_key, *fuzzy_sets
|
48
|
+
Lunar.redis.expire dist_key, Lunar.ttl
|
49
|
+
end
|
50
|
+
UnsortedResultSet.new(dist_key, &block)
|
51
|
+
end
|
30
52
|
end
|
31
53
|
end
|
32
54
|
|
data/lib/lunar/sets.rb
CHANGED
@@ -14,6 +14,27 @@ module Lunar
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class FuzzySets < Array
|
18
|
+
attr :prefix, :words, :field
|
19
|
+
|
20
|
+
def initialize(prefix, keywords, field)
|
21
|
+
@prefix = prefix
|
22
|
+
@field = field
|
23
|
+
@words = Words.new(keywords)
|
24
|
+
|
25
|
+
super(redis_set_keys)
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def redis_set_keys
|
30
|
+
words.map { |w| ns[Lunar.encode(w)] }
|
31
|
+
end
|
32
|
+
|
33
|
+
def ns
|
34
|
+
@ns ||= Lunar.nest[prefix][:Fuzzy][field]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
17
38
|
class RangeSets < Array
|
18
39
|
attr :prefix, :field, :range
|
19
40
|
|
@@ -28,10 +49,8 @@ module Lunar
|
|
28
49
|
def write_and_retrieve_key
|
29
50
|
zrange = Lunar.redis.zrangebyscore(Lunar.nest[prefix][field],
|
30
51
|
@range.first, @range.last)
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
# TODO :expire the key in X seconds where X is customizable
|
52
|
+
|
53
|
+
zrange.each { |id| Lunar.redis.zadd key, 1, id }
|
35
54
|
key.to_s
|
36
55
|
end
|
37
56
|
|
data/lunar.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{lunar}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Cyril David"]
|
12
|
-
s.date = %q{2010-05-
|
12
|
+
s.date = %q{2010-05-06}
|
13
13
|
s.description = %q{uses sorted sets and sets, sorting by score}
|
14
14
|
s.email = %q{cyx.ucron@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"examples/ohm.rb",
|
27
27
|
"lib/lunar.rb",
|
28
|
+
"lib/lunar/fuzzy_word.rb",
|
28
29
|
"lib/lunar/index.rb",
|
29
30
|
"lib/lunar/result_set.rb",
|
30
31
|
"lib/lunar/scoring.rb",
|
@@ -34,6 +35,8 @@ Gem::Specification.new do |s|
|
|
34
35
|
"lunar.gemspec",
|
35
36
|
"test/helper.rb",
|
36
37
|
"test/test_lunar.rb",
|
38
|
+
"test/test_lunar_fuzzy.rb",
|
39
|
+
"test/test_lunar_fuzzy_word.rb",
|
37
40
|
"test/test_lunar_index.rb",
|
38
41
|
"test/test_lunar_scoring.rb",
|
39
42
|
"test/test_lunar_search.rb",
|
@@ -80,6 +83,8 @@ Gem::Specification.new do |s|
|
|
80
83
|
s.test_files = [
|
81
84
|
"test/helper.rb",
|
82
85
|
"test/test_lunar.rb",
|
86
|
+
"test/test_lunar_fuzzy.rb",
|
87
|
+
"test/test_lunar_fuzzy_word.rb",
|
83
88
|
"test/test_lunar_index.rb",
|
84
89
|
"test/test_lunar_scoring.rb",
|
85
90
|
"test/test_lunar_search.rb",
|
@@ -93,11 +98,14 @@ Gem::Specification.new do |s|
|
|
93
98
|
|
94
99
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
95
100
|
s.add_development_dependency(%q<contest>, [">= 0"])
|
101
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
96
102
|
else
|
97
103
|
s.add_dependency(%q<contest>, [">= 0"])
|
104
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
98
105
|
end
|
99
106
|
else
|
100
107
|
s.add_dependency(%q<contest>, [">= 0"])
|
108
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
101
109
|
end
|
102
110
|
end
|
103
111
|
|
data/test/helper.rb
CHANGED
@@ -0,0 +1,118 @@
|
|
1
|
+
#
|
2
|
+
# module Lunar
|
3
|
+
# module Fuzzy
|
4
|
+
#
|
5
|
+
# end
|
6
|
+
# end
|
7
|
+
require "helper"
|
8
|
+
|
9
|
+
class LunarFuzzyTest < Test::Unit::TestCase
|
10
|
+
setup do
|
11
|
+
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
12
|
+
Lunar.redis.flushdb
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when setting fuzzy name, 'Yukihiro Matsumoto'" do
|
16
|
+
setup do
|
17
|
+
@index = Lunar::Index.create 'Item' do |i|
|
18
|
+
i.key 1001
|
19
|
+
i.fuzzy :name, 'Yukihiro Matsumoto'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
should "store Lunar:Item:name:Y up to o and M up to o" do
|
24
|
+
fname, lname = 'yukihiro', 'matsumoto'
|
25
|
+
|
26
|
+
(1..fname.length).each do |length|
|
27
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(fname[0, length]) }"
|
28
|
+
assert Lunar.redis.smembers(key).include?('1001')
|
29
|
+
end
|
30
|
+
|
31
|
+
(1..lname.length).each do |length|
|
32
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(lname[0, length]) }"
|
33
|
+
assert Lunar.redis.smembers(key).include?('1001')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when creating an index that already exists" do
|
39
|
+
setup do
|
40
|
+
@index = Lunar::Index.create 'Item' do |i|
|
41
|
+
i.key 1001
|
42
|
+
i.fuzzy :name, 'Yukihiro Matsumoto'
|
43
|
+
end
|
44
|
+
|
45
|
+
@index = Lunar::Index.create 'Item' do |i|
|
46
|
+
i.key 1001
|
47
|
+
i.fuzzy :name, 'Martin Fowler Yuki'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
should "remove all fuzzy entries for Yukihiro Matsumoto" do
|
52
|
+
fname, lname = 'yukihiro', 'matsumoto'
|
53
|
+
|
54
|
+
(5..fname.length).each do |length|
|
55
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(fname[0, length]) }"
|
56
|
+
assert ! Lunar.redis.smembers(key).include?('1001')
|
57
|
+
end
|
58
|
+
|
59
|
+
(3..lname.length).each do |length|
|
60
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(lname[0, length]) }"
|
61
|
+
assert ! Lunar.redis.smembers(key).include?('1001')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
should "store Lunar:Item:name:M up to n and F up to r etc..." do
|
66
|
+
fname, lname, triple = 'martin', 'fowler', 'yuki'
|
67
|
+
|
68
|
+
(1..fname.length).each do |length|
|
69
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(fname[0, length]) }"
|
70
|
+
assert Lunar.redis.smembers(key).include?('1001')
|
71
|
+
end
|
72
|
+
|
73
|
+
(1..lname.length).each do |length|
|
74
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(lname[0, length]) }"
|
75
|
+
assert Lunar.redis.smembers(key).include?('1001')
|
76
|
+
end
|
77
|
+
|
78
|
+
(1..triple.length).each do |length|
|
79
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(triple[0, length]) }"
|
80
|
+
assert Lunar.redis.smembers(key).include?('1001')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "on delete" do
|
86
|
+
setup do
|
87
|
+
@index = Lunar::Index.create 'Item' do |i|
|
88
|
+
i.key 1001
|
89
|
+
i.fuzzy :name, 'Yukihiro Matsumoto'
|
90
|
+
end
|
91
|
+
|
92
|
+
Lunar::Index.delete('Item', 1001)
|
93
|
+
end
|
94
|
+
|
95
|
+
should "remove all fuzzy entries for Yukihiro Matsumoto" do
|
96
|
+
fname, lname = 'yukihiro', 'matsumoto'
|
97
|
+
|
98
|
+
(0..fname.length).each do |length|
|
99
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(fname[0, length]) }"
|
100
|
+
assert ! Lunar.redis.smembers(key).include?('1001')
|
101
|
+
end
|
102
|
+
|
103
|
+
(0..lname.length).each do |length|
|
104
|
+
key = "Lunar:Item:Fuzzy:name:#{ encode(lname[0, length]) }"
|
105
|
+
assert ! Lunar.redis.smembers(key).include?('1001')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
should "also remove the key Lunar:Item:Fuzzy:1001:name" do
|
110
|
+
assert ! Lunar.redis.exists("Lunar:Item:Fuzzy:1001:name")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def encode(str)
|
116
|
+
Lunar.encode(str)
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class LunarFuzzyWordTest < Test::Unit::TestCase
|
4
|
+
context "the word 'dictionary'" do
|
5
|
+
setup do
|
6
|
+
@w = Lunar::FuzzyWord.new('dictionary')
|
7
|
+
end
|
8
|
+
|
9
|
+
should "have d, di, ... dictionary as it's partials" do
|
10
|
+
assert_equal ['d', 'di', 'dic', 'dict', 'dicti', 'dictio',
|
11
|
+
'diction', 'dictiona', 'dictionar', 'dictionary'], @w.partials
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/test/test_lunar_index.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require "helper"
|
2
2
|
|
3
3
|
class LunarIndexTest < Test::Unit::TestCase
|
4
|
+
setup do
|
5
|
+
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
6
|
+
Lunar.redis.flushdb
|
7
|
+
end
|
8
|
+
|
4
9
|
context "given Item" do
|
5
10
|
setup do
|
6
11
|
@index = Lunar::Index.new('Item')
|
@@ -32,9 +37,6 @@ class LunarIndexTest < Test::Unit::TestCase
|
|
32
37
|
|
33
38
|
context "on create" do
|
34
39
|
setup do
|
35
|
-
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
36
|
-
Lunar.redis.flushdb
|
37
|
-
|
38
40
|
@index = Lunar::Index.create 'Item' do |i|
|
39
41
|
i.key 1001
|
40
42
|
i.attr :name, 'iphone 3G'
|
@@ -82,9 +84,6 @@ class LunarIndexTest < Test::Unit::TestCase
|
|
82
84
|
|
83
85
|
context "when creating an index that already exists" do
|
84
86
|
setup do
|
85
|
-
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
86
|
-
Lunar.redis.flushdb
|
87
|
-
|
88
87
|
@index = Lunar::Index.create 'Item' do |i|
|
89
88
|
i.key 1001
|
90
89
|
i.attr :name, 'iphone 3G'
|
@@ -135,9 +134,6 @@ class LunarIndexTest < Test::Unit::TestCase
|
|
135
134
|
|
136
135
|
context "on delete" do
|
137
136
|
setup do
|
138
|
-
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
139
|
-
Lunar.redis.flushdb
|
140
|
-
|
141
137
|
@index = Lunar::Index.create 'Item' do |i|
|
142
138
|
i.key 1001
|
143
139
|
i.attr :name, 'iphone 3G'
|
@@ -166,9 +162,6 @@ class LunarIndexTest < Test::Unit::TestCase
|
|
166
162
|
|
167
163
|
context "on create of an index with numeric scores" do
|
168
164
|
setup do
|
169
|
-
Lunar.redis(Redis.new(:host => '127.0.0.1', :port => '6380'))
|
170
|
-
Lunar.redis.flushdb
|
171
|
-
|
172
165
|
@index = Lunar::Index.create 'Item' do |i|
|
173
166
|
i.key 1001
|
174
167
|
i.integer :cost, 2700
|
data/test/test_lunar_search.rb
CHANGED
@@ -6,7 +6,19 @@ class Item < Struct.new(:id)
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
+
class Person < Struct.new(:id)
|
10
|
+
def self.[](id)
|
11
|
+
new(id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
9
17
|
class LunarSearchTest < Test::Unit::TestCase
|
18
|
+
setup do
|
19
|
+
Lunar.redis.flushdb
|
20
|
+
end
|
21
|
+
|
10
22
|
context "searching when there exists no index yet" do
|
11
23
|
should "return an empty set" do
|
12
24
|
items = Lunar.search(Item, "foobar")
|
@@ -155,4 +167,89 @@ class LunarSearchTest < Test::Unit::TestCase
|
|
155
167
|
assert items.map(&:id).include?('1003')
|
156
168
|
end
|
157
169
|
end
|
170
|
+
|
171
|
+
context "given Martin Fowler, Chad Fowler, and Frank Macallen" do
|
172
|
+
setup do
|
173
|
+
Lunar::Index.create "Person" do |i|
|
174
|
+
i.key 1001
|
175
|
+
i.fuzzy :name, 'Martin Fowler'
|
176
|
+
end
|
177
|
+
|
178
|
+
Lunar::Index.create "Person" do |i|
|
179
|
+
i.key 1002
|
180
|
+
i.fuzzy :name, 'Chad Fowler'
|
181
|
+
end
|
182
|
+
|
183
|
+
Lunar::Index.create "Person" do |i|
|
184
|
+
i.key 1003
|
185
|
+
i.fuzzy :name, 'Frank Macallen'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
should "return Martin and Frank when searching M, and Ma" do
|
190
|
+
res1 = Lunar.search(Person, :fuzzy => { :name => 'M' })
|
191
|
+
res2 = Lunar.search(Person, :fuzzy => { :name => 'Ma' })
|
192
|
+
|
193
|
+
assert_equal %w{1001 1003}, res1.map(&:id)
|
194
|
+
assert_equal %w{1001 1003}, res2.map(&:id)
|
195
|
+
end
|
196
|
+
|
197
|
+
should "return only Martin when searching Mar up to Martin" do
|
198
|
+
res1 = Lunar.search(Person, :fuzzy => { :name => 'Mar' })
|
199
|
+
res2 = Lunar.search(Person, :fuzzy => { :name => 'Mart' })
|
200
|
+
res3 = Lunar.search(Person, :fuzzy => { :name => 'Marti' })
|
201
|
+
res4 = Lunar.search(Person, :fuzzy => { :name => 'Martin' })
|
202
|
+
|
203
|
+
assert_equal %w{1001}, res1.map(&:id)
|
204
|
+
assert_equal %w{1001}, res2.map(&:id)
|
205
|
+
assert_equal %w{1001}, res3.map(&:id)
|
206
|
+
assert_equal %w{1001}, res4.map(&:id)
|
207
|
+
end
|
208
|
+
|
209
|
+
should "return only Frank when searching Mac up to Macallen" do
|
210
|
+
res1 = Lunar.search(Person, :fuzzy => { :name => 'Mac' })
|
211
|
+
res2 = Lunar.search(Person, :fuzzy => { :name => 'Maca' })
|
212
|
+
res3 = Lunar.search(Person, :fuzzy => { :name => 'Macal' })
|
213
|
+
res4 = Lunar.search(Person, :fuzzy => { :name => 'Macall' })
|
214
|
+
res5 = Lunar.search(Person, :fuzzy => { :name => 'Macalle' })
|
215
|
+
res6 = Lunar.search(Person, :fuzzy => { :name => 'Macallen' })
|
216
|
+
|
217
|
+
assert_equal %w{1003}, res1.map(&:id)
|
218
|
+
assert_equal %w{1003}, res2.map(&:id)
|
219
|
+
assert_equal %w{1003}, res3.map(&:id)
|
220
|
+
assert_equal %w{1003}, res4.map(&:id)
|
221
|
+
assert_equal %w{1003}, res5.map(&:id)
|
222
|
+
assert_equal %w{1003}, res6.map(&:id)
|
223
|
+
end
|
224
|
+
|
225
|
+
should "return the three of them when searching F" do
|
226
|
+
res1 = Lunar.search(Person, :fuzzy => { :name => 'F' })
|
227
|
+
|
228
|
+
assert_equal %w{1001 1002 1003}, res1.map(&:id)
|
229
|
+
end
|
230
|
+
|
231
|
+
should "return the 2 fowlers when searching fo up to fowler" do
|
232
|
+
res1 = Lunar.search(Person, :fuzzy => { :name => 'Fo' })
|
233
|
+
res2 = Lunar.search(Person, :fuzzy => { :name => 'Fow' })
|
234
|
+
res3 = Lunar.search(Person, :fuzzy => { :name => 'Fowl' })
|
235
|
+
res4 = Lunar.search(Person, :fuzzy => { :name => 'Fowle' })
|
236
|
+
res5 = Lunar.search(Person, :fuzzy => { :name => 'Fowler' })
|
237
|
+
|
238
|
+
assert_equal %w{1001 1002}, res1.map(&:id)
|
239
|
+
assert_equal %w{1001 1002}, res2.map(&:id)
|
240
|
+
assert_equal %w{1001 1002}, res3.map(&:id)
|
241
|
+
assert_equal %w{1001 1002}, res4.map(&:id)
|
242
|
+
assert_equal %w{1001 1002}, res5.map(&:id)
|
243
|
+
end
|
244
|
+
|
245
|
+
should "return be able to expire the stored union" do
|
246
|
+
Lunar.stubs(:ttl).returns(1)
|
247
|
+
search = Lunar::Search.new(Person, :fuzzy => { :name => 'Fo' })
|
248
|
+
search.results
|
249
|
+
|
250
|
+
assert Lunar.redis.exists search.send(:dist_key)
|
251
|
+
sleep 2
|
252
|
+
assert ! Lunar.redis.exists(search.send(:dist_key))
|
253
|
+
end
|
254
|
+
end
|
158
255
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 2
|
8
7
|
- 3
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Cyril David
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-06 00:00:00 +08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -29,6 +29,18 @@ dependencies:
|
|
29
29
|
version: "0"
|
30
30
|
type: :development
|
31
31
|
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: mocha
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id002
|
32
44
|
description: uses sorted sets and sets, sorting by score
|
33
45
|
email: cyx.ucron@gmail.com
|
34
46
|
executables: []
|
@@ -47,6 +59,7 @@ files:
|
|
47
59
|
- VERSION
|
48
60
|
- examples/ohm.rb
|
49
61
|
- lib/lunar.rb
|
62
|
+
- lib/lunar/fuzzy_word.rb
|
50
63
|
- lib/lunar/index.rb
|
51
64
|
- lib/lunar/result_set.rb
|
52
65
|
- lib/lunar/scoring.rb
|
@@ -56,6 +69,8 @@ files:
|
|
56
69
|
- lunar.gemspec
|
57
70
|
- test/helper.rb
|
58
71
|
- test/test_lunar.rb
|
72
|
+
- test/test_lunar_fuzzy.rb
|
73
|
+
- test/test_lunar_fuzzy_word.rb
|
59
74
|
- test/test_lunar_index.rb
|
60
75
|
- test/test_lunar_scoring.rb
|
61
76
|
- test/test_lunar_search.rb
|
@@ -126,6 +141,8 @@ summary: a minimalistic full text search implementation in redis
|
|
126
141
|
test_files:
|
127
142
|
- test/helper.rb
|
128
143
|
- test/test_lunar.rb
|
144
|
+
- test/test_lunar_fuzzy.rb
|
145
|
+
- test/test_lunar_fuzzy_word.rb
|
129
146
|
- test/test_lunar_index.rb
|
130
147
|
- test/test_lunar_scoring.rb
|
131
148
|
- test/test_lunar_search.rb
|